* 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 <sys/wait.h>
#include <sys/prctl.h>
#include "profiler/heap_profiler_listener.h"
#include "ecmascript/dfx/hprof/heap_profiler.h"
#include "ecmascript/checkpoint/thread_state_transition.h"
#include "ecmascript/dfx/hprof/heap_snapshot.h"
#include "ecmascript/dfx/hprof/rawheap_dump.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"
#include "ecmascript/platform/backtrace.h"
#include "ecmascript/platform/process.h"
#include "ecmascript/platform/file.h"
#include "ecmascript/runtime_lock.h"
#if defined(ENABLE_DUMP_IN_FAULTLOG)
#include "faultloggerd_client.h"
#endif
#ifdef ENABLE_HISYSEVENT
#include "hisysevent.h"
#include "dfx_signal_handler.h"
#include "client/memory_collector_client.h"
#endif
namespace panda::ecmascript {
bool HeapProfiler::oomDumpActive_ = false;
std::pair<bool, NodeId> EntryIdMap::FindId(JSTaggedType addr)
{
auto it = idMap_.find(addr);
if (it == idMap_.end()) {
return std::make_pair(false, GetNextId());
} else {
return std::make_pair(true, it->second);
}
}
NodeId EntryIdMap::FindOrInsertNodeId(JSTaggedType addr)
{
auto it = idMap_.find(addr);
if (it != idMap_.end()) {
return it->second;
}
NodeId id = GetNextId();
idMap_.emplace(addr, id);
return id;
}
bool EntryIdMap::InsertId(JSTaggedType addr, NodeId id)
{
auto [iter, inserted] = idMap_.insert_or_assign(addr, id);
return inserted;
}
bool EntryIdMap::EraseId(JSTaggedType addr)
{
return idMap_.erase(addr);
}
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_.insert_or_assign(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();
}
HeapMarker marker {};
auto nodes = snapshot->GetNodes();
for (auto node : *nodes) {
auto addr = node->GetAddress();
if (JSTaggedValue{addr}.IsHeapObject()) {
marker.Mark(addr);
}
}
RemoveUnmarkedObjects(marker);
}
void EntryIdMap::RemoveUnmarkedObjects(HeapMarker &marker)
{
for (auto it = idMap_.begin(); it != idMap_.end();) {
JSTaggedType addr = it->first;
if (JSTaggedValue{addr}.IsHeapObject() && !marker.IsMarked(addr)) {
it = idMap_.erase(it);
} else {
++it;
}
}
}
HeapProfiler::HeapProfiler(const EcmaVM *vm) : vm_(vm), stringTable_(vm), chunk_(vm->GetNativeAreaAllocator())
{
isProfiling_ = false;
entryIdMap_ = GetChunk()->New<EntryIdMap>();
if (g_isEnableCMCGC) {
moveEventCbId_ = common::HeapProfilerListener::GetInstance().RegisterMoveEventCb(
[this](uintptr_t fromObj, uintptr_t toObj, size_t size) {
this->MoveEvent(fromObj, reinterpret_cast<TaggedObject *>(toObj), size);
});
}
}
HeapProfiler::~HeapProfiler()
{
JSPandaFileManager::GetInstance()->ClearNameMap();
ClearSnapshot();
GetChunk()->Delete(entryIdMap_);
if (g_isEnableCMCGC) {
common::HeapProfilerListener::GetInstance().UnRegisterMoveEventCb(moveEventCbId_);
}
}
void HeapProfiler::AllocationEvent(TaggedObject *address, size_t size)
{
LockHolder lock(mutex_);
DISALLOW_GARBAGE_COLLECTION;
if (isProfiling_) {
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<JSTaggedType>(address), reinterpret_cast<JSTaggedType>(forwardAddress));
if (heapTracker_ != nullptr) {
heapTracker_->MoveEvent(address, forwardAddress, size);
}
}
}
void HeapProfiler::UpdateHeapObjects(HeapSnapshot *snapshot)
{
SharedHeap::GetInstance()->GetSweeper()->WaitAllTaskFinished();
vm_->GetHeap()->WaitAndHandleCCFinished();
snapshot->UpdateNodes();
}
void HeapProfiler::DumpHeapSnapshotForOOM([[maybe_unused]] const DumpSnapShotOption &dumpOption,
[[maybe_unused]] bool fromSharedGC)
{
#if defined(ENABLE_DUMP_IN_FAULTLOG)
DumpSnapShotOption doDumpOption = dumpOption;
#if defined(PANDA_TARGET_ARM32)
doDumpOption.dumpFormat = DumpFormat::JSON;
doDumpOption.isSimplify = true;
doDumpOption.isBeforeFill = false;
#endif
int32_t fd;
if (doDumpOption.isDumpOOM && doDumpOption.dumpFormat == DumpFormat::BINARY) {
fd = RequestFileDescriptor(static_cast<int32_t>(FaultLoggerType::JS_RAW_SNAPSHOT));
} else {
fd = RequestFileDescriptor(static_cast<int32_t>(FaultLoggerType::JS_HEAP_SNAPSHOT));
}
if (fd < 0) {
LOG_ECMA(ERROR) << "OOM Dump Write FD failed, fd" << fd;
SEND_HISYSEVENT(ARKTS_RUNTIME, ARK_STATS_OOM, STATISTIC, "STATUS", 1,
"MESSAGE", "request fd from dfx failed.",
"OOM_TYPE", dumpOption.isForSharedOOM ? "SHARED_OOM" : "LOCAL_OOM");
return;
}
FileDescriptorStream stream(fd);
if (fromSharedGC) {
DumpHeapSnapshotFromSharedGCForOOM(&stream, doDumpOption);
} else {
DumpHeapSnapshot(&stream, doDumpOption);
}
#else
LOG_ECMA(ERROR) << "oom dump not supported.";
#endif
}
void HeapProfiler::DumpHeapSnapshotFromSharedGCForOOM(Stream *stream, const DumpSnapShotOption &dumpOption)
{
if (!TryStartOOMDump()) {
LOG_ECMA(WARN) << "OOM dump already in progress, skip dump";
return;
}
SharedHeap::GetInstance()->PrepareByJSThread(vm_->GetAssociatedJSThread(), true);
if (dumpOption.isProcDump) {
Runtime::GetInstance()->GCIterateThreadList([&](JSThread *thread) {
const_cast<Heap*>(thread->GetEcmaVM()->GetHeap())->Prepare();
});
} else {
const_cast<Heap*>(vm_->GetHeap())->Prepare();
}
appPid_ = getpid();
appTid_ = syscall(SYS_gettid);
pid_t pid = -1;
if ((pid = fork()) < 0) {
LOG_ECMA(ERROR) << "DumpHeapSnapshotFromSharedGCForOOM fork failed: " << strerror(errno);
return;
}
if (pid == 0) {
prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("dump_process"), 0, 0, 0);
DumpHeapSnapshotFromSharedGC(stream, dumpOption);
_exit(0);
}
if (pid != 0) {
if (!Process::IsolateSubProcess(vm_->GetBundleName().c_str(), getpid(), pid)) {
LOG_ECMA(ERROR) << "OOM Fork dump snapshot failed!";
}
}
}
bool HeapProfiler::DumpHeapSnapshotFromSharedGC(Stream *stream, const DumpSnapShotOption &dumpOption)
{
base::BlockHookScope blockScope;
Runtime::GetInstance()->GCIterateThreadList([&](JSThread *thread) {
ASSERT(thread->IsSuspended() || thread->HasLaunchedSuspendAll());
const_cast<Heap*>(thread->GetEcmaVM()->GetHeap())->FillBumpPointerForTlab();
});
if (dumpOption.dumpFormat == DumpFormat::BINARY) {
BinaryDump(stream, dumpOption);
} else {
DoDump(stream, nullptr, dumpOption);
}
stream->EndOfStream();
return true;
}
bool HeapProfiler::DoDump(Stream *stream, Progress *progress, const DumpSnapShotOption &dumpOption)
{
DISALLOW_GARBAGE_COLLECTION;
int32_t heapCount = 0;
size_t heapSize = 0;
HeapSnapshot *snapshot = nullptr;
{
if (dumpOption.isFullGC) {
if (g_isEnableCMCGC) {
heapSize = common::Heap::GetHeap().GetSurvivedSize();
heapCount = static_cast<int32_t>(common::Heap::GetHeap().GetAllocatedSize());
} else {
heapSize = vm_->GetHeap()->GetLiveObjectSize();
heapCount = static_cast<int32_t>(vm_->GetHeap()->GetHeapObjectCount());
}
LOG_ECMA(INFO) << "HeapProfiler DumpSnapshot heap size " << heapSize;
LOG_ECMA(INFO) << "HeapProfiler DumpSnapshot heap count " << heapCount;
if (progress != nullptr) {
progress->ReportProgress(0, heapCount);
}
}
snapshot = MakeHeapSnapshot(SampleType::ONE_SHOT, dumpOption);
ASSERT(snapshot != nullptr);
}
bool writeMap = false;
if (dumpOption.isSync) {
entryIdMap_->UpdateEntryIdMap(snapshot);
if (dumpOption.nativeAddrToNodeIdMap != 0) {
writeMap = true;
UpdateNodeAddressIdMap();
snapshot->SetNodeAddressIdMap(nodeAddressIdMap_);
}
}
isProfiling_ = true;
if (progress != nullptr) {
progress->ReportProgress(heapCount, heapCount);
}
auto serializeAndDelete = [&](Stream* targetStream) -> bool {
HeapSnapshotJSONSerializer::Serialize(snapshot, targetStream);
if (writeMap) HeapSnapshotJSONSerializer::SerializeExtraInfo(snapshot, targetStream);
GetChunk()->Delete(snapshot);
return true;
};
if (!stream->Good()) {
FileStream newStream(GenDumpFileName(dumpOption.dumpFormat));
return serializeAndDelete(&newStream);
}
return serializeAndDelete(stream);
}
void HeapProfiler::UpdateNodeAddressIdMap()
{
class NodeAddressRootVisitor final : public RootVisitor {
public:
explicit NodeAddressRootVisitor(CUnorderedMap<uintptr_t, NodeId> &nodeAddrIdMap,
EntryIdMap *entryIdMap)
: nodeAddrIdMap_(nodeAddrIdMap), entryIdMap_(entryIdMap) {}
~NodeAddressRootVisitor() = default;
void VisitRoot(Root type, ObjectSlot slot) override
{
if (type != Root::ROOT_LOCAL_HANDLE && type != Root::ROOT_GLOBAL_HANDLE) {
return;
}
auto it = entryIdMap_->GetIdMap()->find(slot.GetTaggedType());
if (it != entryIdMap_->GetIdMap()->end()) {
nodeAddrIdMap_[slot.SlotAddress()] = it->second;
}
}
void VisitRangeRoot(Root type, ObjectSlot start, ObjectSlot end) override
{
for (ObjectSlot slot = start; slot < end; slot++) {
VisitRoot(type, slot);
}
}
void VisitBaseAndDerivedRoot(Root type, ObjectSlot base, ObjectSlot derived,
[[maybe_unused]] uintptr_t baseOldObject) override {}
private:
CUnorderedMap<uintptr_t, NodeId> &nodeAddrIdMap_;
EntryIdMap *entryIdMap_;
};
JSThread *thread = vm_->GetAssociatedJSThread();
EcmaVM* ecmaVm = thread->GetEcmaVM();
ThreadManagedScope managedScope(thread);
NodeAddressRootVisitor visitor(nodeAddressIdMap_, entryIdMap_);
ecmaVm->IterateHandle(visitor);
thread->Iterate(visitor, GlobalVisitType::ALL_GLOBAL_VISIT);
Runtime::GetInstance()->IterateSendableGlobalStorage(visitor);
}
[[maybe_unused]]static void WaitProcess(pid_t pid, const std::function<void(uint8_t)> &callback)
{
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) {
LOG_GC(ERROR) << "DumpHeapSnapshot wait failed ";
if (callback) {
callback(static_cast<uint8_t>(DumpHeapSnapshotStatus::FAILED_TO_WAIT));
}
return;
}
if (p == pid) {
if (callback) {
callback(static_cast<uint8_t>(DumpHeapSnapshotStatus::SUCCESS));
}
return;
}
if (time(nullptr) > startTime + DUMP_TIME_OUT) {
LOG_GC(ERROR) << "DumpHeapSnapshot kill thread, wait " << DUMP_TIME_OUT << " s";
kill(pid, SIGKILL);
if (callback) {
callback(static_cast<uint8_t>(DumpHeapSnapshotStatus::WAIT_FORK_PROCESS_TIMEOUT));
}
return;
}
usleep(DEFAULT_SLEEP_TIME);
}
}
bool HeapProfiler::BinaryDump(Stream *stream, const DumpSnapShotOption &dumpOption)
{
#if defined(PANDA_TARGET_ARM32)
LOG_ECMA(ERROR) << "binary dump is not supported in ARM32.";
return false;
#endif
[[maybe_unused]] EcmaHandleScope ecmaHandleScope(vm_->GetAssociatedJSThread());
DumpSnapShotOption option;
std::vector<std::string> filePaths;
std::vector<uint64_t> fileSizes;
std::string filePath;
auto stringTable = chunk_.New<StringHashMap>(vm_);
auto snapshot = chunk_.New<HeapSnapshot>(vm_, stringTable, option, false, entryIdMap_);
if (const_cast<EcmaVM *>(vm_)->GetJSOptions().EnableRawHeapCrop()) {
Runtime::GetInstance()->SetRawHeapDumpCropLevel(RawHeapDumpCropLevel::LEVEL_V2);
}
RawHeapDump *rawHeapDump = nullptr;
RawHeapDumpCropLevel cropLevel = Runtime::GetInstance()->GetRawHeapDumpCropLevel();
switch (cropLevel) {
case RawHeapDumpCropLevel::LEVEL_V1:
rawHeapDump = new RawHeapDumpV1(vm_, stream, snapshot, entryIdMap_, dumpOption);
break;
case RawHeapDumpCropLevel::LEVEL_V2:
rawHeapDump = new RawHeapDumpV2(vm_, stream, snapshot, entryIdMap_, dumpOption);
break;
default:
LOG_ECMA(FATAL) << "rawheap dump, do not supported crop level " << static_cast<int>(cropLevel);
UNREACHABLE();
break;
}
rawHeapDump->BinaryDump();
filePath = RAWHEAP_FILE_NAME + "-" + std::to_string(appPid_) + "-" + std::to_string(appTid_) + ".rawheap";
filePaths.emplace_back(filePath);
fileSizes.emplace_back(rawHeapDump->GetRawHeapFileOffset());
delete rawHeapDump;
vm_->GetEcmaGCKeyStats()->SendSysEventDataSize(filePaths, fileSizes);
chunk_.Delete<StringHashMap>(stringTable);
chunk_.Delete<HeapSnapshot>(snapshot);
return true;
}
void HeapProfiler::FillIdMap()
{
HeapMarker marker {};
SharedHeap* sHeap = SharedHeap::GetInstance();
if (sHeap != nullptr) {
sHeap->IterateOverObjects([this, &marker](TaggedObject *obj) {
JSTaggedType addr = ((JSTaggedValue)obj).GetRawData();
auto [idExist, sequenceId] = entryIdMap_->FindId(addr);
entryIdMap_->InsertId(addr, sequenceId);
marker.Mark(addr);
});
sHeap->GetReadOnlySpace()->IterateOverObjects([this, &marker](TaggedObject *obj) {
JSTaggedType addr = ((JSTaggedValue)obj).GetRawData();
auto [idExist, sequenceId] = entryIdMap_->FindId(addr);
entryIdMap_->InsertId(addr, sequenceId);
marker.Mark(addr);
});
}
auto heap = vm_->GetHeap();
if (heap != nullptr) {
heap->IterateOverObjects([this, &marker](TaggedObject *obj) {
JSTaggedType addr = ((JSTaggedValue)obj).GetRawData();
auto [idExist, sequenceId] = entryIdMap_->FindId(addr);
entryIdMap_->InsertId(addr, sequenceId);
marker.Mark(addr);
});
}
entryIdMap_->RemoveUnmarkedObjects(marker);
}
bool HeapProfiler::DumpHeapSnapshot(Stream *stream, const DumpSnapShotOption &dumpOption, Progress *progress,
std::function<void(uint8_t)> callback)
{
base::BlockHookScope blockScope;
ThreadManagedScope managedScope(vm_->GetAssociatedJSThread());
pid_t pid = -1;
{
if (dumpOption.isFullGC) {
if (g_isEnableCMCGC) {
common::BaseRuntime::RequestGC(common::GC_REASON_BACKUP, false, common::GC_TYPE_FULL);
} else {
[[maybe_unused]] bool heapClean = ForceFullGC(vm_);
ForceSharedGC();
ASSERT(heapClean);
}
}
JSThread *thread = vm_->GetAssociatedJSThread();
RuntimeLockHolder locker(thread, SharedHeap::GetInstance()->GetSuspensionRequestMutex());
SuspendAllScope suspendScope(thread);
if (g_isEnableCMCGC) {
common::Heap::GetHeap().WaitForGCFinish();
} else {
const_cast<Heap*>(vm_->GetHeap())->Prepare();
SharedHeap::GetInstance()->PrepareByJSThread(thread, true);
Runtime::GetInstance()->GCIterateThreadList([&](JSThread *thread) {
ASSERT(thread->IsSuspended() || thread->HasLaunchedSuspendAll());
const_cast<Heap*>(thread->GetEcmaVM()->GetHeap())->FillBumpPointerForTlab();
});
}
ASSERT(!vm_->GetAssociatedJSThread()->IsConcurrentCopying());
if (dumpOption.isSync) {
if (dumpOption.dumpFormat == DumpFormat::BINARY) {
return BinaryDump(stream, dumpOption);
} else {
return DoDump(stream, progress, dumpOption);
}
}
AppFreezeFilterCallback appfreezeCallback = Runtime::GetInstance()->GetAppFreezeFilterCallback();
std::string unused;
if (appfreezeCallback != nullptr && !appfreezeCallback(getpid(), false, unused)) {
LOG_ECMA(ERROR) << "failed to set appfreeze filter";
return false;
}
if (dumpOption.isBeforeFill) {
FillIdMap();
}
if (dumpOption.isDumpOOM) {
if (!TryStartOOMDump()) {
LOG_ECMA(WARN) << "OOM dump already in progress, skip dump";
return false;
}
}
pid = ForkAndPerformDump(stream, dumpOption, progress);
if (pid < 0) {
if (callback) {
callback(static_cast<uint8_t>(DumpHeapSnapshotStatus::FORK_FAILED));
}
return false;
}
}
if (pid != 0) {
if (dumpOption.isDumpOOM) {
if (!Process::IsolateSubProcess(vm_->GetBundleName().c_str(), getpid(), pid)) {
LOG_ECMA(ERROR) << "OOM Fork dump snapshot failed!";
return false;
}
return true;
}
std::thread thread(&WaitProcess, pid, callback);
thread.detach();
}
isProfiling_ = true;
return true;
}
pid_t HeapProfiler::ForkAndPerformDump(Stream *stream,
const DumpSnapShotOption &dumpOption,
Progress *progress)
{
appPid_ = getpid();
appTid_ = syscall(SYS_gettid);
pid_t pid = fork();
if (pid < 0) {
LOG_ECMA(ERROR) << "DumpHeapSnapshot fork failed: " << strerror(errno);
return -1;
}
if (pid == 0) {
vm_->GetAssociatedJSThread()->SetCrossThreadExecution(true);
prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("dump_process"), 0, 0, 0);
if (dumpOption.dumpFormat == DumpFormat::BINARY) {
BinaryDump(stream, dumpOption);
stream->EndOfStream();
} else {
DoDump(stream, progress, dumpOption);
}
vm_->GetAssociatedJSThread()->SetCrossThreadExecution(false);
_exit(0);
}
return pid;
}
bool HeapProfiler::StartHeapTracking(double timeInterval, bool isVmMode, Stream *stream,
bool traceAllocation, bool newThread)
{
if (g_isEnableCMCGC) {
common::BaseRuntime::RequestGC(common::GC_REASON_BACKUP, false, common::GC_TYPE_FULL);
} else {
vm_->CollectGarbage(TriggerGCType::OLD_GC);
ForceSharedGC();
}
JSThread *thread = vm_->GetAssociatedJSThread();
RuntimeLockHolder locker(thread, SharedHeap::GetInstance()->GetSuspensionRequestMutex());
SuspendAllScope suspendScope(thread);
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<HeapTracker>(snapshot, timeInterval, stream);
const_cast<EcmaVM *>(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;
}
{
JSThread *thread = vm_->GetAssociatedJSThread();
RuntimeLockHolder locker(thread, SharedHeap::GetInstance()->GetSuspensionRequestMutex());
SuspendAllScope suspendScope(thread);
UpdateHeapObjects(snapshot);
snapshot->RecordSampleTime();
}
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<int32_t>(vm_->GetHeap()->GetHeapObjectCount());
const_cast<EcmaVM *>(vm_)->StopHeapTracking();
if (newThread) {
heapTracker_->StopTracing();
}
HeapSnapshot *snapshot = heapTracker_->GetHeapSnapshot();
if (snapshot == nullptr) {
return false;
}
if (progress != nullptr) {
progress->ReportProgress(0, heapCount);
}
{
if (g_isEnableCMCGC) {
common::BaseRuntime::RequestGC(common::GC_REASON_BACKUP, false, common::GC_TYPE_FULL);
SuspendAllScope suspendScope(vm_->GetAssociatedJSThread());
snapshot->FinishSnapshot();
} else {
ForceSharedGC();
JSThread *thread = vm_->GetAssociatedJSThread();
RuntimeLockHolder locker(thread, SharedHeap::GetInstance()->GetSuspensionRequestMutex());
SuspendAllScope suspendScope(thread);
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<Heap *>(vm->GetHeap())->CollectGarbage(TriggerGCType::FULL_GC);
return true;
}
return false;
}
void HeapProfiler::ForceSharedGC()
{
SharedHeap *sHeap = SharedHeap::GetInstance();
sHeap->CollectGarbage<TriggerGCType::SHARED_GC, GCReason::OTHER>(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<Heap *>(vm_->GetHeap())->Prepare();
}
ASSERT(!vm_->GetAssociatedJSThread()->IsConcurrentCopying());
switch (sampleType) {
case SampleType::ONE_SHOT: {
auto *snapshot = GetChunk()->New<HeapSnapshot>(vm_, GetEcmaStringTable(), dumpOption,
traceAllocation, entryIdMap_);
if (snapshot == nullptr) {
LOG_FULL(FATAL) << "alloc snapshot failed";
UNREACHABLE();
}
snapshot->BuildUp(dumpOption.isSimplify);
return snapshot;
}
case SampleType::REAL_TIME: {
auto *snapshot = GetChunk()->New<HeapSnapshot>(vm_, GetEcmaStringTable(), dumpOption,
traceAllocation, entryIdMap_);
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<HeapSampling>(vm_, const_cast<Heap *>(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();
}
bool HeapProfiler::IsStartLocalHandleLeakDetect() const
{
return startLocalHandleLeakDetect_;
}
void HeapProfiler::SwitchStartLocalHandleLeakDetect()
{
startLocalHandleLeakDetect_ = !startLocalHandleLeakDetect_;
}
void HeapProfiler::IncreaseScopeCount()
{
++scopeCount_;
}
void HeapProfiler::DecreaseScopeCount()
{
--scopeCount_;
}
uint32_t HeapProfiler::GetScopeCount() const
{
return scopeCount_;
}
void HeapProfiler::PushToActiveScopeStack(LocalScope *localScope, EcmaHandleScope *ecmaHandleScope)
{
activeScopeStack_.emplace(std::make_shared<ScopeWrapper>(localScope, ecmaHandleScope));
}
void HeapProfiler::PopFromActiveScopeStack()
{
if (!activeScopeStack_.empty()) {
activeScopeStack_.pop();
}
}
std::shared_ptr<ScopeWrapper> HeapProfiler::GetLastActiveScope() const
{
if (!activeScopeStack_.empty()) {
return activeScopeStack_.top();
}
return nullptr;
}
void HeapProfiler::ClearHandleBackTrace()
{
handleBackTrace_.clear();
}
std::string_view HeapProfiler::GetBackTraceOfHandle(const uintptr_t handle) const
{
const auto it = handleBackTrace_.find(handle);
if (it != handleBackTrace_.end()) {
return std::string_view(it->second);
}
return "";
}
bool HeapProfiler::InsertHandleBackTrace(uintptr_t handle, const std::string &backTrace)
{
auto [iter, inserted] = handleBackTrace_.insert_or_assign(handle, backTrace);
return inserted;
}
void HeapProfiler::WriteToLeakStackTraceFd(std::ostringstream &buffer) const
{
if (leakStackTraceFd_ < 0) {
return;
}
buffer << std::endl;
DPrintf(reinterpret_cast<fd_t>(leakStackTraceFd_), buffer.str());
buffer.str("");
}
void HeapProfiler::SetLeakStackTraceFd(const int32_t fd)
{
FdsanExchangeOwnerTag(reinterpret_cast<fd_t>(fd));
leakStackTraceFd_ = fd;
}
int32_t HeapProfiler::GetLeakStackTraceFd() const
{
return leakStackTraceFd_;
}
void HeapProfiler::CloseLeakStackTraceFd()
{
if (leakStackTraceFd_ != -1) {
FSync(reinterpret_cast<fd_t>(leakStackTraceFd_));
Close(reinterpret_cast<fd_t>(leakStackTraceFd_));
leakStackTraceFd_ = -1;
}
}
void HeapProfiler::StorePotentiallyLeakHandles(const uintptr_t handle)
{
bool isDetectedByScopeCount { GetScopeCount() <= 1 };
bool isDetectedByScopeTime { false };
if (auto lastScope = GetLastActiveScope()) {
auto timeSinceLastScopeCreate = lastScope->clockScope_.TotalSpentTime();
isDetectedByScopeTime = timeSinceLastScopeCreate >= LOCAL_HANDLE_LEAK_TIME_MS;
}
if (isDetectedByScopeCount || isDetectedByScopeTime) {
std::ostringstream stack;
Backtrace(stack, true);
InsertHandleBackTrace(handle, stack.str());
}
}
bool HeapProfiler::TryStartOOMDump()
{
bool result = oomDumpActive_;
oomDumpActive_ = true;
return !result;
}
void HeapProfiler::ResetOOMDump()
{
oomDumpActive_ = false;
}
}