* Copyright (c) 2021-2026 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 "ecmascript/dfx/cpu_profiler/cpu_profiler.h"
#include "ecmascript/compiler/aot_file/aot_file_manager.h"
#include "ecmascript/jspandafile/js_pandafile_manager.h"
#if defined(ENABLE_FFRT_INTERFACES)
#include "c/executor_task.h"
#endif
#if !defined(ECMASCRIPT_SUPPORT_CPUPROFILER)
#error "ECMASCRIPT_SUPPORT_CPUPROFILER not defined"
#endif
namespace panda::ecmascript {
Mutex CpuProfiler::synchronizationMutex_;
CMap<pthread_t, struct TaskInfo> CpuProfiler::profilerMap_ = CMap<pthread_t, struct TaskInfo>();
CpuProfiler::CpuProfiler(const EcmaVM *vm, const int interval) : vm_(vm), interval_(interval)
{
enableVMTag_ = const_cast<EcmaVM *>(vm)->GetJSOptions().EnableCpuProfilerVMTag();
generator_ = new SamplesRecord();
generator_->SetEnableVMTag(enableVMTag_);
generator_->SetSourceMapTranslateCallback(vm->GetSourceMapTranslateCallback());
generator_->NodeInit();
if (generator_->SemInit(0, 0, 0) != 0) {
LOG_ECMA(ERROR) << "sem_[0] init failed";
}
if (generator_->SemInit(1, 0, 0) != 0) {
LOG_ECMA(ERROR) << "sem_[1] init failed";
}
if (generator_->SemInit(2, 0, 0) != 0) {
LOG_ECMA(ERROR) << "sem_[2] init failed";
}
}
bool CpuProfiler::RegisterGetStackSignal()
{
struct sigaction sa;
sa.sa_sigaction = &GetStackSignalHandler;
if (sigemptyset(&sa.sa_mask) != 0) {
LOG_ECMA(ERROR) << "CpuProfiler::RegisterGetStackSignal, sigemptyset failed, errno = " << errno;
return false;
}
sa.sa_flags = SA_RESTART | SA_SIGINFO;
if (sigaction(SIGPROF, &sa, nullptr) != 0) {
LOG_ECMA(ERROR) << "CpuProfiler::RegisterGetStackSignal, sigaction failed, errno = " << errno;
return false;
}
return true;
}
bool CpuProfiler::StartCpuProfilerForInfo()
{
LOG_ECMA(INFO) << "CpuProfiler::StartCpuProfilerForInfo, sampling interval = " << interval_;
if (isProfiling_) {
LOG_ECMA(ERROR) << "CpuProfiler::StartCpuProfilerForInfo, can not start when CpuProfiler is Profiling";
return false;
}
if (!RegisterGetStackSignal()) {
return false;
}
tid_ = static_cast<pthread_t>(JSThread::GetCurrentThreadId());
void *taskHandle = nullptr;
#if defined(ENABLE_FFRT_INTERFACES)
taskHandle = ffrt_get_cur_task();
TaskInfo taskInfo { vm_, taskHandle };
#else
TaskInfo taskInfo { vm_, nullptr };
#endif
{
LockHolder lock(synchronizationMutex_);
profilerMap_[tid_] = taskInfo;
}
JSPandaFileManager *pandaFileManager = JSPandaFileManager::GetInstance();
pandaFileManager->EnumerateJSPandaFiles([&](const std::shared_ptr<JSPandaFile> &file) -> bool {
pandaFileManager->CpuProfilerGetJSPtExtractor(file.get());
return true;
});
generator_->SetTimeDeltaThreshold(interval_ * THRESHOLD_GROWTH_FACTORY + THRESHOLD_FIXED_INCREMENT);
generator_->SetIsStart(true);
uint64_t startTime = SamplingProcessor::GetMicrosecondsTimeStamp();
generator_->SetThreadStartTime(startTime);
auto *params = new RunParams(generator_, static_cast<uint32_t>(interval_), pthread_self(), taskHandle);
if (pthread_create(&tid_, nullptr, SamplingProcessor::Run, params) != 0) {
LOG_ECMA(ERROR) << "CpuProfiler::StartCpuProfilerForInfo, pthread_create failed, errno = " << errno;
delete params;
return false;
}
isProfiling_ = true;
vm_->GetJSThread()->SetIsProfiling(true);
outToFile_ = false;
return true;
}
bool CpuProfiler::StartCpuProfilerForFile(const std::string &fileName)
{
LOG_ECMA(INFO) << "CpuProfiler::StartCpuProfilerForFile, sampling interval = " << interval_;
if (isProfiling_) {
LOG_ECMA(ERROR) << "CpuProfiler::StartCpuProfilerForFile, can not start when CpuProfiler is Profiling";
return false;
}
std::string absoluteFilePath("");
if (!CheckFileName(fileName, absoluteFilePath)) {
return false;
}
fileName_ = absoluteFilePath;
generator_->SetFileName(fileName_);
generator_->fileHandle_.open(fileName_.c_str());
if (generator_->fileHandle_.fail()) {
LOG_ECMA(ERROR) << "CpuProfiler::StartCpuProfilerForFile, fileHandle_ open failed";
return false;
}
if (!RegisterGetStackSignal()) {
return false;
}
tid_ = static_cast<pthread_t>(JSThread::GetCurrentThreadId());
void *taskHandle = nullptr;
#if defined(ENABLE_FFRT_INTERFACES)
taskHandle = ffrt_get_cur_task();
TaskInfo taskInfo { vm_, taskHandle };
#else
TaskInfo taskInfo { vm_, nullptr };
#endif
{
LockHolder lock(synchronizationMutex_);
profilerMap_[tid_] = taskInfo;
}
JSPandaFileManager *pandaFileManager = JSPandaFileManager::GetInstance();
pandaFileManager->EnumerateJSPandaFiles([&](const std::shared_ptr<JSPandaFile> &file) -> bool {
pandaFileManager->CpuProfilerGetJSPtExtractor(file.get());
return true;
});
generator_->SetTimeDeltaThreshold(interval_ * THRESHOLD_GROWTH_FACTORY + THRESHOLD_FIXED_INCREMENT);
generator_->SetIsStart(true);
uint64_t startTime = SamplingProcessor::GetMicrosecondsTimeStamp();
generator_->SetThreadStartTime(startTime);
auto *params = new RunParams(generator_, static_cast<uint32_t>(interval_), pthread_self(), taskHandle);
if (pthread_create(&tid_, nullptr, SamplingProcessor::Run, params) != 0) {
LOG_ECMA(ERROR) << "CpuProfiler::StartCpuProfilerForFile, pthread_create failed, errno = " << errno;
delete params;
return false;
}
isProfiling_ = true;
vm_->GetJSThread()->SetIsProfiling(true);
outToFile_ = true;
return true;
}
bool CpuProfiler::StopCpuProfilerForInfo(std::unique_ptr<struct ProfileInfo> &profileInfo)
{
LOG_ECMA(INFO) << "CpuProfiler::StopCpuProfilerForInfo enter";
if (!isProfiling_) {
LOG_ECMA(WARN) << "CpuProfiler::StopCpuProfilerForInfo, not isProfiling_";
return true;
}
if (outToFile_) {
LOG_ECMA(ERROR) << "CpuProfiler::StopCpuProfilerForInfo, is outToFile_";
return false;
}
generator_->SetIsStart(false);
if (generator_->SemPost(0) != 0) {
LOG_ECMA(ERROR) << "CpuProfiler::StopCpuProfilerForInfo, sem_[0] post failed, errno = " << errno;
return false;
}
if (generator_->SemWait(1) != 0) {
LOG_ECMA(ERROR) << "CpuProfiler::StopCpuProfilerForInfo, sem_[1] wait failed, errno = " << errno;
return false;
}
isProfiling_ = false;
vm_->GetJSThread()->SetIsProfiling(false);
profileInfo = generator_->GetProfileInfo();
return true;
}
void CpuProfiler::SetCpuSamplingInterval(int interval)
{
interval_ = static_cast<uint32_t>(interval);
}
bool CpuProfiler::StopCpuProfilerForFile()
{
LOG_ECMA(INFO) << "CpuProfiler::StopCpuProfilerForFile enter";
if (!isProfiling_) {
LOG_ECMA(WARN) << "CpuProfiler::StopCpuProfilerForFile, not isProfiling_";
return true;
}
if (!outToFile_) {
LOG_ECMA(ERROR) << "CpuProfiler::StopCpuProfilerForFile, not outToFile_";
return false;
}
generator_->SetIsStart(false);
if (generator_->SemPost(0) != 0) {
LOG_ECMA(ERROR) << "CpuProfiler::StopCpuProfilerForFile, sem_[0] post failed, errno = " << errno;
return false;
}
if (generator_->SemWait(1) != 0) {
LOG_ECMA(ERROR) << "CpuProfiler::StopCpuProfilerForFile, sem_[1] wait failed, errno = " << errno;
return false;
}
isProfiling_ = false;
vm_->GetJSThread()->SetIsProfiling(false);
generator_->StringifySampleData();
std::string fileData = generator_->GetSampleData();
generator_->fileHandle_ << fileData;
return true;
}
CpuProfiler::~CpuProfiler()
{
if (generator_->SemDestroy(0) != 0) {
LOG_ECMA(ERROR) << "sem_[0] destroy failed";
}
if (generator_->SemDestroy(1) != 0) {
LOG_ECMA(ERROR) << "sem_[1] destroy failed";
}
if (generator_->SemDestroy(2) != 0) {
LOG_ECMA(ERROR) << "sem_[2] destroy failed";
}
if (generator_ != nullptr) {
delete generator_;
generator_ = nullptr;
}
}
void CpuProfiler::GetStack(FrameIterator &it)
{
const CMap<struct MethodKey, struct FrameInfo> &stackInfo = generator_->GetStackInfo();
bool topFrame = true;
generator_->ResetFrameLength();
for (; !it.Done(); it.Advance<>()) {
auto method = it.CheckAndGetMethod();
if (method == nullptr || !JSTaggedValue(method).IsMethod()) {
continue;
}
bool isNative = method->IsNativeWithCallField();
struct MethodKey methodKey;
methodKey.deoptType = method->GetDeoptType();
if (topFrame) {
methodKey.state = JsStackGetter::GetRunningState(it, vm_, isNative, true, enableVMTag_);
topFrame = false;
} else {
methodKey.state = JsStackGetter::GetRunningState(it, vm_, isNative, false, enableVMTag_);
}
if (isNative) {
JsStackGetter::GetCallLineNumber(it, vm_, methodKey.lineNumber);
}
void *methodIdentifier = JsStackGetter::GetMethodIdentifier(method, it, vm_);
if (methodIdentifier == nullptr) {
continue;
}
methodKey.methodIdentifier = methodIdentifier;
if (stackInfo.count(methodKey) == 0) {
struct FrameInfoTemp codeEntry;
if (UNLIKELY(!JsStackGetter::ParseMethodInfo(methodKey, it, vm_, codeEntry, true))) {
continue;
}
if (UNLIKELY(!generator_->PushStackInfo(codeEntry))) {
return;
}
}
if (UNLIKELY(!generator_->PushFrameStack(methodKey))) {
return;
}
}
generator_->PostFrame();
}
bool CpuProfiler::GetStackBeforeCallNapi(JSThread *thread)
{
uint64_t tempTimeStamp = SamplingProcessor::GetMicrosecondsTimeStamp();
if (tempTimeStamp - beforeCallNapiTimeStamp_ < interval_) {
return false;
}
if (GetStackCallNapi(thread, true)) {
beforeCallNapiTimeStamp_ = tempTimeStamp;
return true;
}
return false;
}
void CpuProfiler::GetStackAfterCallNapi(JSThread *thread)
{
GetStackCallNapi(thread, false);
}
bool CpuProfiler::GetStackCallNapi(JSThread *thread, bool beforeCallNapi)
{
[[maybe_unused]] CallNapiScope scope(this);
const CMap<struct MethodKey, struct FrameInfo> &stackInfo = generator_->GetStackInfo();
generator_->ClearNapiStack();
bool topFrame = true;
auto currentFrame = const_cast<JSTaggedType *>(thread->GetCurrentFrame());
FrameIterator it(currentFrame, thread);
if (!beforeCallNapi) {
it.Advance<GCVisitedFlag::IGNORED>();
}
for (; !it.Done(); it.Advance<GCVisitedFlag::IGNORED>()) {
auto method = it.CheckAndGetMethod();
if (method == nullptr || !JSTaggedValue(method).IsMethod()) {
continue;
}
bool isNative = method->IsNativeWithCallField();
struct MethodKey methodKey;
methodKey.deoptType = method->GetDeoptType();
if (topFrame) {
if (beforeCallNapi) {
methodKey.state = RunningState::NAPI;
} else {
methodKey.state = JsStackGetter::GetRunningState(it, vm_, isNative, true, enableVMTag_);
}
topFrame = false;
} else {
methodKey.state = JsStackGetter::GetRunningState(it, vm_, isNative, false, enableVMTag_);
}
if (isNative) {
JsStackGetter::GetCallLineNumber(it, vm_, methodKey.lineNumber);
}
void *methodIdentifier = JsStackGetter::GetMethodIdentifier(method, it, vm_);
if (methodIdentifier == nullptr) {
continue;
}
methodKey.methodIdentifier = methodIdentifier;
if (stackInfo.count(methodKey) == 0) {
struct FrameInfoTemp codeEntry;
if (UNLIKELY(!JsStackGetter::ParseMethodInfo(methodKey, it, vm_, codeEntry, true))) {
continue;
}
if (UNLIKELY(!generator_->PushNapiStackInfo(codeEntry))) {
return false;
}
}
if (UNLIKELY(!generator_->PushNapiFrameStack(methodKey))) {
return false;
}
}
generator_->PostNapiFrame();
return true;
}
void CpuProfiler::GetStackSignalHandler(int signal, [[maybe_unused]] siginfo_t *siginfo, void *context)
{
if (signal != SIGPROF) {
return;
}
CpuProfiler *profiler = nullptr;
JSThread *thread = nullptr;
{
LockHolder lock(synchronizationMutex_);
pthread_t tid = static_cast<pthread_t>(GetThreadIdOrCachedTaskId());
auto it = profilerMap_.find(tid);
if (it == profilerMap_.end()) {
LOG_ECMA(ERROR) << "CpuProfiler GetStackSignalHandler tid not found: " << tid;
return;
}
const EcmaVM *vm = it->second.vm_;
if (vm == nullptr) {
LOG_ECMA(ERROR) << "CpuProfiler GetStackSignalHandler vm is nullptr";
return;
}
profiler = vm->GetProfiler();
thread = vm->GetAssociatedJSThread();
if (profiler == nullptr) {
LOG_ECMA(ERROR) << "CpuProfiler GetStackSignalHandler profiler is nullptr";
return;
}
}
[[maybe_unused]] SignalStateScope scope(thread->GetEcmaVM()->GetJsDebuggerManager());
if (profiler->GetBuildNapiStack() || thread->GetGcState()) {
if (profiler->generator_->SemPost(0) != 0) {
LOG_ECMA(ERROR) << "sem_[0] post failed";
}
return;
}
uint64_t pc = 0;
if (thread->IsAsmInterpreter()) {
if (!thread->GetEcmaVM()->GetAOTFileManager()->TryReadLock()) {
if (profiler->generator_->SemPost(0) != 0) {
LOG_ECMA(ERROR) << "sem_[0] post failed";
}
return;
}
pc = GetPcFromContext(context);
}
if (thread->IsAsmInterpreter() && profiler->IsAddrAtStubOrAot(pc) &&
!profiler->IsEntryFrameHeaderOrTail(thread, pc)) {
if (profiler->IfNeedSkipBarrierStubHeaderOrTail(thread, pc)) {
return;
}
[[maybe_unused]] ucontext_t *ucontext = reinterpret_cast<ucontext_t*>(context);
[[maybe_unused]] mcontext_t &mcontext = ucontext->uc_mcontext;
[[maybe_unused]] void *fp = nullptr;
[[maybe_unused]] void *sp = nullptr;
#if defined(PANDA_TARGET_AMD64)
fp = reinterpret_cast<void*>(mcontext.gregs[REG_RBP]);
sp = reinterpret_cast<void*>(mcontext.gregs[REG_RSP]);
#elif defined(PANDA_TARGET_ARM64)
fp = reinterpret_cast<void*>(mcontext.regs[29]);
sp = reinterpret_cast<void*>(mcontext.sp);
#else
LOG_FULL(FATAL) << "AsmInterpreter does not currently support other platforms, please run on x64 and arm64";
return;
#endif
if (sp > fp) {
LOG_ECMA(ERROR) << "sp > fp, stack frame exception";
if (profiler->generator_->SemPost(0) != 0) {
LOG_ECMA(ERROR) << "sem_[0] post failed";
}
return;
}
if (JsStackGetter::CheckFrameType(thread, reinterpret_cast<JSTaggedType *>(fp))) {
FrameIterator it(reinterpret_cast<JSTaggedType *>(fp), thread);
profiler->GetStack(it);
}
} else if (thread->IsAsmInterpreter()) {
if (thread->GetLastLeaveFrame() != nullptr) {
JSTaggedType *leaveFrame = const_cast<JSTaggedType *>(thread->GetLastLeaveFrame());
if (JsStackGetter::CheckFrameType(thread, leaveFrame)) {
FrameIterator it(leaveFrame, thread);
profiler->GetStack(it);
}
}
} else {
if (thread->GetCurrentFrame() != nullptr) {
if (JsStackGetter::CheckFrameType(thread, const_cast<JSTaggedType *>(thread->GetCurrentFrame()))) {
FrameHandler frameHandler(thread);
FrameIterator it(frameHandler.GetSp(), thread);
profiler->GetStack(it);
}
}
}
if (profiler->generator_->SemPost(0) != 0) {
LOG_ECMA(ERROR) << "sem_[0] post failed";
return;
}
}
bool CpuProfiler::InHeaderOrTail(uint64_t pc, uint64_t entryBegin, uint64_t entryDuration, uint64_t headerSize,
uint64_t tailSize) const
{
uintptr_t entryEnd = entryBegin + entryDuration;
if (pc >= entryBegin && pc <= (entryBegin + headerSize)) {
return true;
}
if (pc <= entryEnd && pc >= (entryEnd - tailSize)) {
return true;
}
return false;
}
bool CpuProfiler::IfNeedSkipBarrierStubHeaderOrTail(JSThread *thread, uint64_t pc) const
{
uint64_t headerSize = 20;
uintptr_t entryBegin = thread->GetFastStubEntry(kungfu::CommonStubCSigns::ID::GetValueWithBarrier);
bool needSkip = InHeaderOrTail(pc, entryBegin, 0, headerSize, 0);
return needSkip;
}
bool CpuProfiler::IsEntryFrameHeaderOrTail(JSThread *thread, uint64_t pc) const
{
uint64_t headerSize = 0;
uint64_t tailSize = 0;
uint64_t entryDuration = 0;
Assembler::GetFrameCompletionPos(headerSize, tailSize, entryDuration);
uintptr_t entryBegin = thread->GetRTInterface(kungfu::RuntimeStubCSigns::ID_AsmInterpreterEntry);
bool inAsmInterpreterEntry = InHeaderOrTail(pc, entryBegin, entryDuration, headerSize, tailSize);
entryBegin = thread->GetRTInterface(kungfu::RuntimeStubCSigns::ID_GeneratorReEnterAsmInterp);
bool inGeneratorReEnterAsmInterp = InHeaderOrTail(pc, entryBegin, entryDuration, headerSize, tailSize);
return (inAsmInterpreterEntry || inGeneratorReEnterAsmInterp);
}
uint64_t CpuProfiler::GetPcFromContext(void *context)
{
[[maybe_unused]] ucontext_t *ucontext = reinterpret_cast<ucontext_t*>(context);
[[maybe_unused]] mcontext_t &mcontext = ucontext->uc_mcontext;
uint64_t pc = 0;
#if defined(PANDA_TARGET_AMD64)
pc = static_cast<uint64_t>(mcontext.gregs[REG_RIP]);
#elif defined(PANDA_TARGET_ARM64)
pc = static_cast<uint64_t>(mcontext.pc);
#else
LOG_FULL(FATAL) << "AsmInterpreter does not currently support other platforms, please run on x64 and arm64";
pc = 0;
#endif
return pc;
}
bool CpuProfiler::IsAddrAtStubOrAot(uint64_t pc) const
{
AOTFileManager *loader = vm_->GetAOTFileManager();
return loader->InsideStub(pc) || loader->InsideAOT(pc);
}
bool CpuProfiler::CheckFileName(const std::string &fileName, std::string &absoluteFilePath) const
{
if (fileName.empty()) {
LOG_ECMA(ERROR) << "CpuProfiler::CheckFileName, fileName is empty";
return false;
}
if (fileName.size() > PATH_MAX) {
LOG_ECMA(ERROR) << "CpuProfiler::CheckFileName, fileName exceed PATH_MAX";
return false;
}
CVector<char> resolvedPath(PATH_MAX);
auto result = realpath(fileName.c_str(), resolvedPath.data());
if (result == nullptr) {
LOG_ECMA(ERROR) << "CpuProfiler::CheckFileName, realpath fail, errno = " << errno;
return false;
}
std::ofstream file(resolvedPath.data());
if (!file.good()) {
LOG_ECMA(ERROR) << "CpuProfiler::CheckFileName, file is not good, errno = " << errno;
return false;
}
file.close();
absoluteFilePath = resolvedPath.data();
return true;
}
void CpuProfiler::SetBuildNapiStack(bool flag)
{
isBuildNapiStack_.store(flag);
}
bool CpuProfiler::GetBuildNapiStack()
{
return isBuildNapiStack_.load();
}
bool CpuProfiler::GetOutToFile()
{
return outToFile_;
}
EcmaVM* CpuProfiler::GetVmbyTid(pthread_t tid)
{
LockHolder lock(synchronizationMutex_);
return const_cast<EcmaVM *>(profilerMap_[tid].vm_);
}
}