/* * Copyright (c) 2021 Huawei Device Co., Ltd. * 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 #include #include #include #include #include #include #include #include #include "ecmascript/dfx/hprof/heap_profiler.h" #include "ecmascript/checkpoint/thread_state_transition.h" #include "ecmascript/dfx/hprof/heap_snapshot.h" #include "ecmascript/jspandafile/js_pandafile_manager.h" #include "ecmascript/mem/heap-inl.h" #include "ecmascript/mem/shared_heap/shared_concurrent_sweeper.h" #include "ecmascript/base/block_hook_scope.h" #include "ecmascript/dfx/hprof/heap_root_visitor.h" #include "ecmascript/mem/object_xray.h" #if defined(ENABLE_DUMP_IN_FAULTLOG) #include "faultloggerd_client.h" #endif namespace panda::ecmascript { static pid_t ForkBySyscall(void) { #ifdef SYS_fork return syscall(SYS_fork); #else return syscall(SYS_clone, SIGCHLD, 0); #endif } std::pair EntryIdMap::FindId(JSTaggedType addr) { auto it = idMap_.find(addr); if (it == idMap_.end()) { return std::make_pair(false, GetNextId()); // return nextId if entry not exits } else { return std::make_pair(true, it->second); } } bool EntryIdMap::InsertId(JSTaggedType addr, NodeId id) { auto it = idMap_.find(addr); if (it == idMap_.end()) { idMap_.emplace(addr, id); return true; } idMap_[addr] = id; return false; } bool EntryIdMap::EraseId(JSTaggedType addr) { auto it = idMap_.find(addr); if (it == idMap_.end()) { return false; } idMap_.erase(it); return true; } bool EntryIdMap::Move(JSTaggedType oldAddr, JSTaggedType forwardAddr) { if (oldAddr == forwardAddr) { return true; } auto it = idMap_.find(oldAddr); if (it != idMap_.end()) { NodeId id = it->second; idMap_.erase(it); idMap_[forwardAddr] = id; return true; } return false; } void EntryIdMap::UpdateEntryIdMap(HeapSnapshot *snapshot) { LOG_ECMA(INFO) << "EntryIdMap::UpdateEntryIdMap"; if (snapshot == nullptr) { LOG_ECMA(FATAL) << "EntryIdMap::UpdateEntryIdMap:snapshot is nullptr"; UNREACHABLE(); } auto nodes = snapshot->GetNodes(); CUnorderedMap newIdMap; for (auto node : *nodes) { auto addr = node->GetAddress(); auto it = idMap_.find(addr); if (it != idMap_.end()) { newIdMap.emplace(addr, it->second); } } idMap_.clear(); idMap_ = newIdMap; } HeapProfiler::HeapProfiler(const EcmaVM *vm) : vm_(vm), stringTable_(vm), chunk_(vm->GetNativeAreaAllocator()) { isProfiling_ = false; entryIdMap_ = GetChunk()->New(); } HeapProfiler::~HeapProfiler() { JSPandaFileManager::GetInstance()->ClearNameMap(); ClearSnapshot(); GetChunk()->Delete(entryIdMap_); } void HeapProfiler::AllocationEvent(TaggedObject *address, size_t size) { DISALLOW_GARBAGE_COLLECTION; if (isProfiling_) { // Id will be allocated later while add new node if (heapTracker_ != nullptr) { heapTracker_->AllocationEvent(address, size); } } } void HeapProfiler::MoveEvent(uintptr_t address, TaggedObject *forwardAddress, size_t size) { LockHolder lock(mutex_); if (isProfiling_) { entryIdMap_->Move(static_cast(address), reinterpret_cast(forwardAddress)); if (heapTracker_ != nullptr) { heapTracker_->MoveEvent(address, forwardAddress, size); } } } void HeapProfiler::UpdateHeapObjects(HeapSnapshot *snapshot) { SharedHeap::GetInstance()->GetSweeper()->WaitAllTaskFinished(); snapshot->UpdateNodes(); } void HeapProfiler::DumpHeapSnapshot([[maybe_unused]] const DumpSnapShotOption &dumpOption) { #if defined(ENABLE_DUMP_IN_FAULTLOG) // Write in faultlog for heap leak. int32_t fd; if (dumpOption.isDumpOOM && dumpOption.dumpFormat == DumpFormat::BINARY) { fd = RequestFileDescriptor(static_cast(FaultLoggerType::JS_RAW_SNAPSHOT)); } else { fd = RequestFileDescriptor(static_cast(FaultLoggerType::JS_HEAP_SNAPSHOT)); } if (fd < 0) { LOG_ECMA(ERROR) << "OOM Dump Write FD failed, fd" << fd; return; } FileDescriptorStream stream(fd); DumpHeapSnapshot(&stream, dumpOption); #endif } bool HeapProfiler::DoDump(Stream *stream, Progress *progress, const DumpSnapShotOption &dumpOption) { int32_t heapCount = 0; HeapSnapshot *snapshot = nullptr; { if (dumpOption.isFullGC) { size_t heapSize = vm_->GetHeap()->GetLiveObjectSize(); LOG_ECMA(INFO) << "HeapProfiler DumpSnapshot heap size " << heapSize; heapCount = static_cast(vm_->GetHeap()->GetHeapObjectCount()); if (progress != nullptr) { progress->ReportProgress(0, heapCount); } } snapshot = MakeHeapSnapshot(SampleType::ONE_SHOT, dumpOption); ASSERT(snapshot != nullptr); } entryIdMap_->UpdateEntryIdMap(snapshot); isProfiling_ = true; if (progress != nullptr) { progress->ReportProgress(heapCount, heapCount); } if (!stream->Good()) { FileStream newStream(GenDumpFileName(dumpOption.dumpFormat)); auto serializerResult = HeapSnapshotJSONSerializer::Serialize(snapshot, &newStream); GetChunk()->Delete(snapshot); return serializerResult; } auto serializerResult = HeapSnapshotJSONSerializer::Serialize(snapshot, stream); GetChunk()->Delete(snapshot); return serializerResult; } static uint64_t CheckAndRemoveWeak(JSTaggedValue &value, uint64_t originalAddr) { if (!value.IsWeak()) { return originalAddr; } JSTaggedValue weakValue(originalAddr); weakValue.RemoveWeakTag(); return weakValue.GetRawData(); } static uint64_t CheckAndAddWeak(JSTaggedValue &value, uint64_t originalAddr) { if (!value.IsWeak()) { return originalAddr; } JSTaggedValue weakValue(originalAddr); weakValue.CreateWeakRef(); return weakValue.GetRawData(); } static uint64_t VisitMember(ObjectSlot &slot, uint64_t objAddr, CUnorderedSet ¬FoundObj, JSHClass *jsHclass, CUnorderedMap &objMap) { auto taggedPointerAddr = reinterpret_cast(slot.SlotAddress()); JSTaggedValue value(reinterpret_cast(*taggedPointerAddr)); auto originalAddr = reinterpret_cast(*taggedPointerAddr); originalAddr = CheckAndRemoveWeak(value, originalAddr); if (!value.IsHeapObject() || originalAddr == 0) { return 0LL; } auto toItemInfo = objMap.find(originalAddr); if (toItemInfo == objMap.end()) { LOG_ECMA(ERROR) << "ark raw heap decode visit " << std::hex << objAddr << ", type=" << JSHClass::DumpJSType(jsHclass->GetObjectType()) << ", not found member old addr=" << originalAddr; notFoundObj.insert(reinterpret_cast(*taggedPointerAddr)); return 0LL; } auto newAddr = reinterpret_cast(toItemInfo->second->Data()); newAddr = CheckAndAddWeak(value, newAddr); slot.Update(reinterpret_cast(newAddr)); return newAddr; } CUnorderedMap> VisitObj(CUnorderedMap &objMap) { CUnorderedSet notFoundObj; CUnorderedMap> refSetMap; // old addr map to ref set auto visitor = [¬FoundObj, &objMap, &refSetMap] (TaggedObject *root, ObjectSlot start, ObjectSlot end, VisitObjectArea area) { if (area == VisitObjectArea::RAW_DATA || area == VisitObjectArea::NATIVE_POINTER) { return; } auto jsHclass = root->GetClass(); auto objAddr = reinterpret_cast(root); CUnorderedSet *refSet = nullptr; if (refSetMap.find(objAddr) != refSetMap.end()) { refSet = &refSetMap[objAddr]; } for (ObjectSlot slot = start; slot < end; slot++) { auto newAddr = VisitMember(slot, objAddr, notFoundObj, jsHclass, objMap); if (jsHclass->IsJsGlobalEnv() && refSet != nullptr && newAddr != 0LL) { refSet->insert(newAddr); } } }; for (auto objInfo : objMap) { auto newAddr = objInfo.second->Data(); auto jsHclassAddr = *reinterpret_cast(newAddr); auto jsHclassItem = objMap.find(jsHclassAddr); if (jsHclassItem == objMap.end()) { LOG_ECMA(ERROR) << "ark raw heap decode hclass not find jsHclassAddr=" << std::hex << jsHclassAddr; continue; } TaggedObject *obj = reinterpret_cast(newAddr); *reinterpret_cast(newAddr) = reinterpret_cast(jsHclassItem->second->Data()); auto jsHclass = reinterpret_cast(jsHclassItem->second->Data()); if (jsHclass->IsString()) { continue; } if (jsHclass->IsJsGlobalEnv()) { refSetMap.emplace(reinterpret_cast(newAddr), CUnorderedSet()); } ObjectXRay::VisitObjectBody(obj, jsHclass, visitor); } if (notFoundObj.size() > 0) { LOG_ECMA(ERROR) << "ark raw heap decode visit obj: not found obj num=" << notFoundObj.size(); } return refSetMap; } static uint64_t GetFileSize(std::string &inputFilePath) { if (inputFilePath.empty()) { return 0; } struct stat fileInfo; if (stat(inputFilePath.c_str(), &fileInfo) == 0) { return fileInfo.st_size; } return 0; } bool ReadFileAtOffset(std::ifstream &file, uint32_t offset, char *buf, uint32_t size) { if (buf == nullptr) { LOG_ECMA(ERROR) << "ark raw heap decode file buf is nullptr"; return false; } if (!file.is_open()) { LOG_ECMA(ERROR) << "ark raw heap decode file not open"; return false; } file.clear(); if (!file.seekg(offset)) { LOG_ECMA(ERROR) << "ark raw heap decode file set offset failed, offset=" << offset; return false; } if (file.read(buf, size).fail()) { LOG_ECMA(ERROR) << "ark raw heap decode file read failed, offset=" << offset; return false; } return true; } CUnorderedMap DecodeMemObj(std::ifstream &file, CVector §ions) { CUnorderedMap objMap; // old addr map to new obj uint32_t heapTotalSize = 0; uint32_t objTotalNum = 0; for (uint32_t sec = 4; sec + 1 < sections.size(); sec += 2) { // 2 :step is 2 uint32_t offset = sections[sec]; uint32_t secHead[2]; if (!ReadFileAtOffset(file, offset, reinterpret_cast(secHead), sizeof(secHead))) { LOG_ECMA(ERROR) << "ark raw heap decode read obj section failed, sec=" << sec << ", offset=" << offset << ", size=" << sections[sec + 1]; return objMap; } LOG_ECMA(INFO) << "ark raw heap decode read obj section failed, sec=" << sec << ", offset=" << offset << ", size=" << sections[sec + 1] << ", obj num=" << secHead[0]; auto tbSize = secHead[0] * sizeof(AddrTableItem); if (secHead[1] != sizeof(AddrTableItem) || tbSize == 0 || tbSize > MAX_OBJ_SIZE) { LOG_ECMA(ERROR) << "ark raw heap decode check obj table section=" << sections[sec] << ", head size=" << sizeof(AddrTableItem) << ", but=" << secHead[1] << "or error table size=" << tbSize; continue; } CVector objTabBuf(tbSize); file.read(objTabBuf.data(), tbSize); auto objTab = reinterpret_cast(objTabBuf.data()); offset += sizeof(secHead); objTotalNum += secHead[0]; for (uint32_t i = 0; i < secHead[0]; i++) { heapTotalSize += objTab[i].objSize; auto actSize = i + 1 < secHead[0] ? objTab[i + 1].offset - objTab[i].offset : sections[sec + 1] - objTab[i].offset - sizeof(secHead); if (actSize != objTab[i].objSize && actSize != sizeof(uint64_t)) { auto tabOffset = offset + i * sizeof(AddrTableItem); LOG_ECMA(ERROR) << "ark raw heap decode check obj size i=" << i << std::hex << ", offset=" << tabOffset << ", addr=" << objTab[i].addr << ", size=" << objTab[i].objSize << " but=" << actSize; continue; } objMap.emplace(objTab[i].addr, new NewAddr(actSize, objTab[i].objSize)); auto result = ReadFileAtOffset(file, offset + objTab[i].offset, objMap[objTab[i].addr]->Data(), actSize); if (!result) { LOG_ECMA(ERROR) << "ark raw heap decode read failed, i=" << i << ", base offset=" << offset << ", obj addr=" << objTab[i].addr << ", read size=" << actSize; return objMap; } } } LOG_ECMA(INFO) << "ark raw heap decode read obj, num=" << objTotalNum << ", size=" << heapTotalSize; return objMap; } CUnorderedMap DecodeStrTable(StringHashMap *strTable, std::ifstream &file, uint32_t offset, uint32_t secSize) { uint32_t secHead[2]; if (!ReadFileAtOffset(file, offset, reinterpret_cast(secHead), sizeof(secHead))) { LOG_ECMA(ERROR) << "ark raw heap decode read str table failed, offset=" << offset << ", size=" << secSize; return CUnorderedMap(0); } uint32_t byteNum = secSize - sizeof(secHead); char *charPtr = new char[byteNum]; file.read(charPtr, byteNum); CUnorderedMap strTabMap; // old addr map to str id uint32_t cnt = 0; uint32_t baseOff = 0; while (cnt++ < secHead[0]) { uint32_t *u32Ptr = reinterpret_cast(charPtr + baseOff); auto strOffset = (u32Ptr[1] + 1) * sizeof(uint64_t) + baseOff; auto getSize = strlen(charPtr + strOffset); if (u32Ptr[0] != getSize) { LOG_ECMA(ERROR) << cnt << " ark raw heap decode check str size=" << u32Ptr[0] << ", but=" << getSize<<"\n"; } auto strAddr = strTable->GetString(charPtr + strOffset); uint32_t num = 0; uint64_t *u64Ptr = reinterpret_cast(&u32Ptr[2]); while (num < u32Ptr[1]) { strTabMap[u64Ptr[num]] = strAddr; num++; } baseOff = strOffset + u32Ptr[0] + 1; } delete[] charPtr; LOG_ECMA(INFO) << "ark raw heap decode string table size=" << strTable->GetCapcity(); return strTabMap; } CUnorderedSet DecodeRootTable(std::ifstream &file, uint32_t offset, uint32_t secSize) { uint32_t secHead[2]; if (!ReadFileAtOffset(file, offset, reinterpret_cast(secHead), sizeof(secHead))) { LOG_ECMA(ERROR) << "ark raw heap decode read root table failed, offset=" << offset << ", size=" << secSize; return CUnorderedSet(0); } if (secHead[1] != sizeof(uint64_t)) { LOG_ECMA(ERROR) << "ark raw heap decode error root size, need=" << sizeof(uint32_t) << ", but=" << secHead[0]; return CUnorderedSet(0); } auto checkSize = sizeof(uint64_t) * secHead[0] + sizeof(secHead); if (secSize != checkSize) { LOG_ECMA(ERROR) << "ark raw heap decode check root section size=" << secSize << ", but=" << checkSize; return CUnorderedSet(0); } CVector rootVec(secHead[0]); file.read(reinterpret_cast(rootVec.data()), sizeof(uint64_t) * secHead[0]); CUnorderedSet rootSet; for (auto addr : rootVec) { rootSet.insert(addr); } LOG_ECMA(INFO) << "ark raw heap decode root obj num=" << rootSet.size(); return rootSet; } CVector GetSectionInfo(std::ifstream &file, uint64_t fileSize) { uint32_t secHead[2]; uint32_t fileOffset = fileSize - sizeof(uint32_t) * 2; // 2 : last 2 uint32 file.seekg(fileOffset); file.read(reinterpret_cast(secHead), sizeof(secHead)); if (secHead[1] != sizeof(uint32_t)) { LOG_ECMA(ERROR) << "ark raw heap decode unexpect head, need=" << sizeof(uint32_t) << ", but=" << secHead[0]; return CVector(0); } CVector secInfo(secHead[0]); // last 4 byte is section num auto secInfoSize = secHead[0] * secHead[1]; fileOffset -= secInfoSize; file.seekg(fileOffset); file.read(reinterpret_cast(secInfo.data()), secInfoSize); return secInfo; } void ClearObjMem(CUnorderedMap &objMap) { for (auto objItem : objMap) { delete objItem.second; } objMap.clear(); } bool HeapProfiler::GenerateHeapSnapshot(std::string &inputFilePath, std::string &outputPath) { LOG_ECMA(INFO) << "ark raw heap decode start target=" << outputPath; uint64_t fileSize = GetFileSize(inputFilePath); if (fileSize == 0) { LOG_ECMA(ERROR) << "ark raw heap decode get file size=0"; return false; } std::ifstream file(inputFilePath, std::ios::binary); if (!file.is_open()) { LOG_ECMA(ERROR) << "ark raw heap decode file failed:" << inputFilePath.c_str(); return false; } if (fileSize > MAX_FILE_SIZE) { LOG_ECMA(ERROR) << "ark raw heap decode get file size > 4GB, unsupported"; return false; } CVector sections = GetSectionInfo(file, fileSize); if (sections.size() == 0) { LOG_ECMA(ERROR) << "ark raw heap decode not found section data"; return false; } auto objMap = DecodeMemObj(file, sections); auto refSetMap = VisitObj(objMap); auto rootSet = DecodeRootTable(file, sections[0], sections[1]); auto strTabMap = DecodeStrTable(GetEcmaStringTable(), file, sections[2], sections[3]); file.close(); DumpSnapShotOption dp; auto *snapshot = new HeapSnapshot(vm_, GetEcmaStringTable(), dp, false, entryIdMap_, GetChunk()); LOG_ECMA(INFO) << "ark raw heap decode generate nodes=" << objMap.size(); snapshot->GenerateNodeForBinMod(objMap, rootSet, strTabMap); rootSet.clear(); strTabMap.clear(); LOG_ECMA(INFO) << "ark raw heap decode fill edges=" << objMap.size(); snapshot->BuildSnapshotForBinMod(objMap, refSetMap); refSetMap.clear(); ClearObjMem(objMap); if (outputPath.empty()) { outputPath = GenDumpFileName(dp.dumpFormat); } else if (outputPath.back() == '/') { outputPath += GenDumpFileName(dp.dumpFormat); } LOG_ECMA(INFO) << "ark raw heap decode serialize file=" << outputPath.c_str(); FileStream newStream(outputPath); auto serializerResult = HeapSnapshotJSONSerializer::Serialize(snapshot, &newStream); delete snapshot; LOG_ECMA(INFO) << "ark raw heap decode finish"; return serializerResult; } [[maybe_unused]]static void WaitProcess(pid_t pid) { time_t startTime = time(nullptr); constexpr int DUMP_TIME_OUT = 300; constexpr int DEFAULT_SLEEP_TIME = 100000; while (true) { int status = 0; pid_t p = waitpid(pid, &status, WNOHANG); if (p < 0 || p == pid) { break; } if (time(nullptr) > startTime + DUMP_TIME_OUT) { LOG_GC(ERROR) << "DumpHeapSnapshot kill thread, wait " << DUMP_TIME_OUT << " s"; kill(pid, SIGTERM); break; } usleep(DEFAULT_SLEEP_TIME); } } template void IterateSharedHeap(Callback &cb) { auto heap = SharedHeap::GetInstance(); heap->GetOldSpace()->IterateOverObjects(cb); heap->GetCompressSpace()->IterateOverObjects(cb); heap->GetNonMovableSpace()->IterateOverObjects(cb); heap->GetHugeObjectSpace()->IterateOverObjects(cb); heap->GetAppSpawnSpace()->IterateOverObjects(cb); heap->GetReadOnlySpace()->IterateOverObjects(cb); } std::pair GetHeapCntAndSize(const EcmaVM *vm) { uint64_t cnt = 0; uint64_t objectSize = 0; auto cb = [&objectSize, &cnt]([[maybe_unused]] TaggedObject *obj) { objectSize += obj->GetClass()->SizeFromJSHClass(obj); ++cnt; }; vm->GetHeap()->IterateOverObjects(cb, false); return std::make_pair(cnt, objectSize); } std::pair GetSharedCntAndSize() { uint64_t cnt = 0; uint64_t size = 0; auto cb = [&cnt, &size](TaggedObject *obj) { cnt++; size += obj->GetClass()->SizeFromJSHClass(obj); }; IterateSharedHeap(cb); return std::make_pair(cnt, size); } static CUnorderedSet GetRootObjects(const EcmaVM *vm) { CUnorderedSet result {}; HeapRootVisitor visitor; uint32_t rootCnt1 = 0; RootVisitor rootEdgeBuilder = [&result, &rootCnt1]( [[maybe_unused]] Root type, ObjectSlot slot) { JSTaggedValue value((slot).GetTaggedType()); if (!value.IsHeapObject()) { return; } ++rootCnt1; TaggedObject *root = value.GetTaggedObject(); result.insert(root); }; RootBaseAndDerivedVisitor rootBaseEdgeBuilder = [] ([[maybe_unused]] Root type, [[maybe_unused]] ObjectSlot base, [[maybe_unused]] ObjectSlot derived, [[maybe_unused]] uintptr_t baseOldObject) { }; uint32_t rootCnt2 = 0; RootRangeVisitor rootRangeEdgeBuilder = [&result, &rootCnt2]([[maybe_unused]] Root type, ObjectSlot start, ObjectSlot end) { for (ObjectSlot slot = start; slot < end; slot++) { JSTaggedValue value((slot).GetTaggedType()); if (!value.IsHeapObject()) { continue; } ++rootCnt2; TaggedObject *root = value.GetTaggedObject(); result.insert(root); } }; visitor.VisitHeapRoots(vm->GetJSThread(), rootEdgeBuilder, rootRangeEdgeBuilder, rootBaseEdgeBuilder); SharedModuleManager::GetInstance()->Iterate(rootEdgeBuilder); Runtime::GetInstance()->IterateCachedStringRoot(rootRangeEdgeBuilder); return result; } size_t GetNotFoundObj(const EcmaVM *vm) { size_t heapTotalSize = 0; CUnorderedSet allHeapObjSet {}; auto handleObj = [&allHeapObjSet, &heapTotalSize](TaggedObject *obj) { allHeapObjSet.insert(obj); uint64_t objSize = obj->GetClass()->SizeFromJSHClass(obj); heapTotalSize += objSize; }; vm->GetHeap()->IterateOverObjects(handleObj, false); vm->GetHeap()->GetCompressSpace()->IterateOverObjects(handleObj); IterateSharedHeap(handleObj); LOG_ECMA(INFO) << "ark raw heap dump GetNotFound heap count:" << allHeapObjSet.size() << ", heap size=" << heapTotalSize; CUnorderedSet notFoundObjSet {}; auto visitor = [¬FoundObjSet, &allHeapObjSet] ([[maybe_unused]]TaggedObject *root, ObjectSlot start, ObjectSlot end, VisitObjectArea area) { if (area == VisitObjectArea::RAW_DATA || area == VisitObjectArea::NATIVE_POINTER) { return; } for (ObjectSlot slot = start; slot < end; slot++) { auto taggedPointerAddr = reinterpret_cast(slot.SlotAddress()); JSTaggedValue value(reinterpret_cast(*taggedPointerAddr)); auto originalAddr = reinterpret_cast(*taggedPointerAddr); if (!value.IsHeapObject() || originalAddr == 0) { continue; } if (value.IsWeakForHeapObject()) { originalAddr -= 1; } if (allHeapObjSet.find(reinterpret_cast(originalAddr)) != allHeapObjSet.end()) { continue; } auto obj = reinterpret_cast(*taggedPointerAddr); if (notFoundObjSet.find(obj) != notFoundObjSet.end()) { continue; } notFoundObjSet.insert(obj); } }; for (auto obj : allHeapObjSet) { ObjectXRay::VisitObjectBody(obj, obj->GetClass(), visitor); } LOG_ECMA(INFO) << "ark raw heap dump GetNotFound not found count:" << notFoundObjSet.size(); return notFoundObjSet.size(); } uint32_t HeapProfiler::CopyObjectMem2Buf(char *objTable, uint32_t objNum, CVector> &memBufMap) { char *currMemBuf = nullptr; auto currSize = 0; uint32_t totalSize = 0; uint32_t curOffset = objNum * sizeof(AddrTableItem); auto objHeaders = reinterpret_cast(objTable); for (uint32_t j = 0; j < objNum; ++j) { auto obj = reinterpret_cast(objHeaders[j].addr); JSTaggedValue value(obj); uint64_t objSize = obj->GetClass()->SizeFromJSHClass(obj); totalSize += objSize; if (currSize + objSize > PER_GROUP_MEM_SIZE || currMemBuf == nullptr) { if (currMemBuf != nullptr) { memBufMap.push_back({currMemBuf, currSize}); } currSize = 0; currMemBuf = chunk_.NewArray(objSize > PER_GROUP_MEM_SIZE? objSize : PER_GROUP_MEM_SIZE); } objHeaders[j].objSize = objSize; objHeaders[j].offset = curOffset; int32_t ret; if (value.IsString()) { CVector strTmp(objSize / sizeof(uint64_t), 0); strTmp[0] = *reinterpret_cast(objHeaders[j].addr); ret = memcpy_s(currMemBuf + currSize, objSize, reinterpret_cast(strTmp.data()), objSize); } else { ret = memcpy_s(currMemBuf + currSize, objSize, reinterpret_cast(objHeaders[j].addr), objSize); } if (ret != 0) { LOG_ECMA(ERROR) << "ark raw heap dump CopyObjectMem memcpy_s failed, currSize=" << currSize << ",objSize=" << objSize << ",addr=" << objHeaders[j].addr; return totalSize; } curOffset += objSize; currSize += objSize; } if (currSize > 0) { memBufMap.push_back({currMemBuf, currSize}); } else if (currMemBuf != nullptr) { chunk_.Delete(currMemBuf); } return totalSize; } uint32_t HeapProfiler::GenObjTable(CUnorderedMap &headerMap, HeapSnapshot *snapshot, CUnorderedMap> &strIdMap) { char *currBuf = chunk_.NewArray(PER_GROUP_MEM_SIZE); uint32_t index = 0; uint32_t objNum = 0; auto table = reinterpret_cast(currBuf); auto handleObj = [&index, &table, &objNum, &headerMap, &currBuf, &snapshot, &strIdMap, this](TaggedObject *obj) { JSTaggedValue value(obj); auto taggedType = value.GetRawData(); auto [exist, id] = entryIdMap_->FindId(taggedType); if (!exist) { entryIdMap_->InsertId(taggedType, id); } table[index].addr = reinterpret_cast(obj); table[index].id = id; auto strId = snapshot->GenerateStringId(obj); if (strId != 1) { // 1 : invalid str id if (strIdMap.find(strId) == strIdMap.end()) { strIdMap.emplace(strId, CVector()); } strIdMap[strId].push_back(table[index].addr); } index++; if (index == HEAD_NUM_PER_GROUP) { headerMap.emplace(currBuf, index); objNum += HEAD_NUM_PER_GROUP; index = 0; currBuf = chunk_.NewArray(PER_GROUP_MEM_SIZE); table = reinterpret_cast(currBuf); } }; vm_->GetHeap()->IterateOverObjects(handleObj, false); vm_->GetHeap()->GetCompressSpace()->IterateOverObjects(handleObj); IterateSharedHeap(handleObj); objNum += index; if (index != 0) { headerMap.emplace(currBuf, index); } else { chunk_.Delete(currBuf); } return objNum; } // 4 byte: root_num // 4 byte: unit size = sizeof(addr), 8 byte here // {8 byte: root obj addr} * root_num uint32_t HeapProfiler::GenRootTable(Stream *stream) { auto roots = GetRootObjects(vm_); auto rootSecHeadSize = 8; // 8 : root num 、 unit size auto rootSecSize = roots.size() * (sizeof(TaggedObject *)) + rootSecHeadSize; auto memBuf = chunk_.NewArray(rootSecSize); uint32_t *rootHeader = reinterpret_cast(memBuf); uint64_t *rootBuf = reinterpret_cast(memBuf + rootSecHeadSize); // 8 : root addr start offset rootHeader[0] = roots.size(); // 0: root num rootHeader[1] = sizeof(TaggedObject *); // 1: unit size auto currInd = 0; for (auto root : roots) { rootBuf[currInd++] = reinterpret_cast(root); } LOG_ECMA(INFO) << "ark raw heap dump GenRootTable root cnt="<WriteBinBlock(memBuf, rootSecSize); chunk_.Delete(memBuf); return rootSecSize; } // 4 byte: obj_num // 4 byte: unit size = sizeof(AddrTableItem) // {AddrTableItem} * obj_num // {obj contents} * obj_num uint32_t HeapProfiler::WriteToBinFile(Stream *stream, char *objTab, uint32_t objNum, CVector> &memBuf) { uint32_t secHeader[] = {objNum, sizeof(AddrTableItem)}; uint32_t secTotalSize = sizeof(secHeader); stream->WriteBinBlock(reinterpret_cast(secHeader), secTotalSize); uint32_t headerSize = objNum * sizeof(AddrTableItem); secTotalSize += headerSize; stream->WriteBinBlock(objTab, headerSize); // write obj header chunk_.Delete(objTab); for (auto memItem : memBuf) { stream->WriteBinBlock(memItem.first, memItem.second); secTotalSize += memItem.second; chunk_.Delete(memItem.first); } return secTotalSize; } bool HeapProfiler::DumpRawHeap(Stream *stream, uint32_t &fileOffset, CVector &secIndexVec) { CUnorderedMap objTabMap; // buf map table num CUnorderedMap> strIdMapObjVec; // string id map to objs vector DumpSnapShotOption op; auto snapshot = GetChunk()->New(vm_, GetEcmaStringTable(), op, false, entryIdMap_, GetChunk()); uint32_t objTotalNum = GenObjTable(objTabMap, snapshot, strIdMapObjVec); LOG_ECMA(INFO) << "ark raw heap dump DumpRawHeap totalObjNumber=" << objTotalNum; CVector>> allMemBuf(objTabMap.size(), CVector>()); CVector threadsVec; CVector objTabVec(objTabMap.size()); uint32_t index = 0; LOG_ECMA(INFO) << "ark raw heap dump DumpRawHeap start to copy, thread num=" << objTabMap.size(); for (auto tableItem : objTabMap) { auto tdCb = [this, &tableItem, &allMemBuf, &index] () { CopyObjectMem2Buf(tableItem.first, tableItem.second, allMemBuf[index]); }; threadsVec.emplace_back(tdCb); objTabVec[index] = tableItem.first; threadsVec[index].join(); ++index; } LOG_ECMA(INFO) << "ark raw heap dump DumpRawHeap write string, num=" << strIdMapObjVec.size(); secIndexVec.push_back(fileOffset); // string table section offset auto size = HeapSnapshotJSONSerializer::DumpStringTable(GetEcmaStringTable(), stream, strIdMapObjVec); secIndexVec.push_back(size); // string table section size GetChunk()->Delete(snapshot); fileOffset += size; strIdMapObjVec.clear(); uint32_t finCnt = 0; LOG_ECMA(INFO) << "ark raw heap dump DumpRawHeap write obj, offset=" << fileOffset; while (finCnt < threadsVec.size()) { for (index = 0; index < threadsVec.size(); ++index) { if (threadsVec[index].joinable()) { // thread not finished continue; } ++finCnt; secIndexVec.push_back(fileOffset); // current section offset auto objNum = objTabMap[objTabVec[index]]; auto currSecSize = WriteToBinFile(stream, objTabVec[index], objNum, allMemBuf[index]); LOG_ECMA(INFO) << "ark raw heap dump DumpRawHeap write offset=" << fileOffset << ", size=" << currSecSize; secIndexVec.push_back(currSecSize); // current section size fileOffset += currSecSize; } } return true; } // * 8 byte: version id // * root table section // * string table section // * {heap section / share heap section} * thread_num // * 4 byte: root table section offset // * 4 byte: root table section size // * 4 byte: string table section offset // * 4 byte: string table section size // * { // * 4 byte: obj section offset // * 4 byte: obj section size // * } * thread_num // * 4 byte: section_offset_num size, 4 byte here // * 4 byte: section_num bool HeapProfiler::BinaryDump(Stream *stream, [[maybe_unused]] const DumpSnapShotOption &dumpOption) { char versionID[VERSION_ID_SIZE] = { 0 }; LOG_ECMA(INFO) << "ark raw heap dump start, version is: " << versionID; stream->WriteBinBlock(versionID, VERSION_ID_SIZE); CQueue> needStrObjQue; // a vector to index all sections, [offset, section_size, offset, section_size, ...] CVector secIndexVec(2); // 2 : section head size uint32_t fileOffset = VERSION_ID_SIZE; secIndexVec[0] = fileOffset; LOG_ECMA(INFO) << "ark raw heap dump GenRootTable"; auto rootSectionSize = GenRootTable(stream); secIndexVec[1] = rootSectionSize; // root section offset fileOffset += rootSectionSize; // root section size DumpRawHeap(stream, fileOffset, secIndexVec); secIndexVec.push_back(secIndexVec.size()); // 4 byte is section num secIndexVec.push_back(sizeof(uint32_t)); // the penultimate is section index data bytes number stream->WriteBinBlock(reinterpret_cast(secIndexVec.data()), secIndexVec.size() *sizeof(uint32_t)); #ifdef OHOS_UNIT_TEST LOG_ECMA(INFO) << "ark raw heap dump UT check obj self-contained"; size_t ret = GetNotFoundObj(vm_); return ret == 0; #else LOG_ECMA(INFO) << "ark raw heap dump finished num=" << secIndexVec.size(); return true; #endif } void HeapProfiler::FillIdMap() { EntryIdMap* newEntryIdMap = GetChunk()->New(); // Iterate SharedHeap Object SharedHeap* sHeap = SharedHeap::GetInstance(); if (sHeap != nullptr) { sHeap->IterateOverObjects([newEntryIdMap, this](TaggedObject *obj) { JSTaggedType addr = ((JSTaggedValue)obj).GetRawData(); auto [idExist, sequenceId] = entryIdMap_->FindId(addr); newEntryIdMap->InsertId(addr, sequenceId); }); } // Iterate LocalHeap Object auto heap = vm_->GetHeap(); if (heap != nullptr) { heap->IterateOverObjects([newEntryIdMap, this](TaggedObject *obj) { JSTaggedType addr = ((JSTaggedValue)obj).GetRawData(); auto [idExist, sequenceId] = entryIdMap_->FindId(addr); newEntryIdMap->InsertId(addr, sequenceId); }); } // copy entryIdMap CUnorderedMap* idMap = entryIdMap_->GetIdMap(); CUnorderedMap* newIdMap = newEntryIdMap->GetIdMap(); *idMap = *newIdMap; GetChunk()->Delete(newEntryIdMap); } bool HeapProfiler::DumpHeapSnapshot(Stream *stream, const DumpSnapShotOption &dumpOption, Progress *progress) { bool res = false; base::BlockHookScope blockScope; ThreadManagedScope managedScope(vm_->GetJSThread()); pid_t pid = -1; { if (dumpOption.isFullGC) { [[maybe_unused]] bool heapClean = ForceFullGC(vm_); ASSERT(heapClean); } SuspendAllScope suspendScope(vm_->GetAssociatedJSThread()); // suspend All. if (dumpOption.isFullGC) { DISALLOW_GARBAGE_COLLECTION; const_cast(vm_->GetHeap())->Prepare(); SharedHeap::GetInstance()->Prepare(true); } Runtime::GetInstance()->GCIterateThreadList([&](JSThread *thread) { ASSERT(!thread->IsInRunningState()); const_cast(thread->GetEcmaVM()->GetHeap())->FillBumpPointerForTlab(); }); if (dumpOption.isBeforeFill) { FillIdMap(); } if (dumpOption.isDumpOOM) { res = BinaryDump(stream, dumpOption); stream->EndOfStream(); return res; } // fork if ((pid = ForkBySyscall()) < 0) { LOG_ECMA(ERROR) << "DumpHeapSnapshot fork failed!"; return false; } if (pid == 0) { vm_->GetAssociatedJSThread()->EnableCrossThreadExecution(); prctl(PR_SET_NAME, reinterpret_cast("dump_process"), 0, 0, 0); res = DoDump(stream, progress, dumpOption); _exit(0); } } if (pid != 0) { if (dumpOption.isSync) { WaitProcess(pid); } else { std::thread thread(&WaitProcess, pid); thread.detach(); } stream->EndOfStream(); } isProfiling_ = true; return res; } bool HeapProfiler::StartHeapTracking(double timeInterval, bool isVmMode, Stream *stream, bool traceAllocation, bool newThread) { vm_->CollectGarbage(TriggerGCType::OLD_GC); ForceSharedGC(); SuspendAllScope suspendScope(vm_->GetAssociatedJSThread()); DumpSnapShotOption dumpOption; dumpOption.isVmMode = isVmMode; dumpOption.isPrivate = false; dumpOption.captureNumericValue = false; HeapSnapshot *snapshot = MakeHeapSnapshot(SampleType::REAL_TIME, dumpOption, traceAllocation); if (snapshot == nullptr) { return false; } isProfiling_ = true; UpdateHeapObjects(snapshot); heapTracker_ = std::make_unique(snapshot, timeInterval, stream); const_cast(vm_)->StartHeapTracking(); if (newThread) { heapTracker_->StartTracing(); } return true; } bool HeapProfiler::UpdateHeapTracking(Stream *stream) { if (heapTracker_ == nullptr) { return false; } HeapSnapshot *snapshot = heapTracker_->GetHeapSnapshot(); if (snapshot == nullptr) { return false; } { vm_->CollectGarbage(TriggerGCType::OLD_GC); ForceSharedGC(); SuspendAllScope suspendScope(vm_->GetAssociatedJSThread()); snapshot->RecordSampleTime(); UpdateHeapObjects(snapshot); } if (stream != nullptr) { snapshot->PushHeapStat(stream); } return true; } bool HeapProfiler::StopHeapTracking(Stream *stream, Progress *progress, bool newThread) { if (heapTracker_ == nullptr) { return false; } int32_t heapCount = static_cast(vm_->GetHeap()->GetHeapObjectCount()); const_cast(vm_)->StopHeapTracking(); if (newThread) { heapTracker_->StopTracing(); } HeapSnapshot *snapshot = heapTracker_->GetHeapSnapshot(); if (snapshot == nullptr) { return false; } if (progress != nullptr) { progress->ReportProgress(0, heapCount); } { ForceSharedGC(); SuspendAllScope suspendScope(vm_->GetAssociatedJSThread()); SharedHeap::GetInstance()->GetSweeper()->WaitAllTaskFinished(); snapshot->FinishSnapshot(); } isProfiling_ = false; if (progress != nullptr) { progress->ReportProgress(heapCount, heapCount); } return HeapSnapshotJSONSerializer::Serialize(snapshot, stream); } std::string HeapProfiler::GenDumpFileName(DumpFormat dumpFormat) { CString filename("hprof_"); switch (dumpFormat) { case DumpFormat::JSON: filename.append(GetTimeStamp()); break; case DumpFormat::BINARY: filename.append("unimplemented"); break; case DumpFormat::OTHER: filename.append("unimplemented"); break; default: filename.append("unimplemented"); break; } filename.append(".heapsnapshot"); return ConvertToStdString(filename); } CString HeapProfiler::GetTimeStamp() { std::time_t timeSource = std::time(nullptr); struct tm tm { }; struct tm *timeData = localtime_r(&timeSource, &tm); if (timeData == nullptr) { LOG_FULL(FATAL) << "localtime_r failed"; UNREACHABLE(); } CString stamp; const int TIME_START = 1900; stamp.append(ToCString(timeData->tm_year + TIME_START)) .append("-") .append(ToCString(timeData->tm_mon + 1)) .append("-") .append(ToCString(timeData->tm_mday)) .append("_") .append(ToCString(timeData->tm_hour)) .append("-") .append(ToCString(timeData->tm_min)) .append("-") .append(ToCString(timeData->tm_sec)); return stamp; } bool HeapProfiler::ForceFullGC(const EcmaVM *vm) { if (vm->IsInitialized()) { const_cast(vm->GetHeap())->CollectGarbage(TriggerGCType::FULL_GC); return true; } return false; } void HeapProfiler::ForceSharedGC() { SharedHeap *sHeap = SharedHeap::GetInstance(); sHeap->CollectGarbage(vm_->GetAssociatedJSThread()); sHeap->GetSweeper()->WaitAllTaskFinished(); } HeapSnapshot *HeapProfiler::MakeHeapSnapshot(SampleType sampleType, const DumpSnapShotOption &dumpOption, bool traceAllocation) { LOG_ECMA(INFO) << "HeapProfiler::MakeHeapSnapshot"; if (dumpOption.isFullGC) { DISALLOW_GARBAGE_COLLECTION; const_cast(vm_->GetHeap())->Prepare(); } switch (sampleType) { case SampleType::ONE_SHOT: { auto *snapshot = GetChunk()->New(vm_, GetEcmaStringTable(), dumpOption, traceAllocation, entryIdMap_, GetChunk()); if (snapshot == nullptr) { LOG_FULL(FATAL) << "alloc snapshot failed"; UNREACHABLE(); } snapshot->BuildUp(dumpOption.isSimplify); return snapshot; } case SampleType::REAL_TIME: { auto *snapshot = GetChunk()->New(vm_, GetEcmaStringTable(), dumpOption, traceAllocation, entryIdMap_, GetChunk()); if (snapshot == nullptr) { LOG_FULL(FATAL) << "alloc snapshot failed"; UNREACHABLE(); } AddSnapshot(snapshot); snapshot->PrepareSnapshot(); return snapshot; } default: return nullptr; } } void HeapProfiler::AddSnapshot(HeapSnapshot *snapshot) { if (hprofs_.size() >= MAX_NUM_HPROF) { ClearSnapshot(); } ASSERT(snapshot != nullptr); hprofs_.emplace_back(snapshot); } void HeapProfiler::ClearSnapshot() { for (auto *snapshot : hprofs_) { GetChunk()->Delete(snapshot); } hprofs_.clear(); } bool HeapProfiler::StartHeapSampling(uint64_t samplingInterval, int stackDepth) { if (heapSampling_.get()) { LOG_ECMA(ERROR) << "Do not start heap sampling twice in a row."; return false; } heapSampling_ = std::make_unique(vm_, const_cast(vm_->GetHeap()), samplingInterval, stackDepth); return true; } void HeapProfiler::StopHeapSampling() { heapSampling_.reset(); } const struct SamplingInfo *HeapProfiler::GetAllocationProfile() { if (!heapSampling_.get()) { LOG_ECMA(ERROR) << "Heap sampling was not started, please start firstly."; return nullptr; } return heapSampling_->GetAllocationProfile(); } } // namespace panda::ecmascript