/*
 * 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()); // return nextId if entry not exits
    } 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_) {
        // 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<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;
    // fork for oom
    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);
    }
    // In async mode, EntryIdMap is filled and updated in parent-process,
    // so EntryIdMap needs to be updated only in sync mode.
    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 {};

    // Iterate SharedHeap Object
    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);
        });
    }

    // Iterate LocalHeap Object
    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); // suspend All.
        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());
        // ide.
        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;
        }
        // hidumper do fork and fillmap.
        if (dumpOption.isBeforeFill) {
            FillIdMap();
        }
        if (dumpOption.isDumpOOM) {
            if (!TryStartOOMDump()) {
                LOG_ECMA(WARN) << "OOM dump already in progress, skip dump";
                return false;
            }
        }
        // fork for hidumper or oom
        pid = ForkAndPerformDump(stream, dumpOption, progress);
        if (pid < 0) {
            if (callback) {
                callback(static_cast<uint8_t>(DumpHeapSnapshotStatus::FORK_FAILED));
            }
            return false;
        }
    }
    if (pid != 0) {
        // if OOM, main thread will destroy itself upon immediate return
        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;
}
}  // namespace panda::ecmascript