/* * 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 "ecmascript/dfx/cpu_profiler/cpu_profiler.h" #include #include #include #include #include #include "ecmascript/jspandafile/js_pandafile_manager.h" #include "ecmascript/taskpool/taskpool.h" namespace panda::ecmascript { CMap CpuProfiler::staticStackInfo_ = CMap(); std::atomic CpuProfiler::singleton_ = nullptr; CMap CpuProfiler::scriptIdMap_ = CMap(); CVector CpuProfiler::staticFrameStack_ = CVector(); os::memory::Mutex CpuProfiler::synchronizationMutex_; sem_t CpuProfiler::sem_[2] = {}; // 2 : sem_ size is two. CpuProfiler::CpuProfiler() { generator_ = new SamplesRecord(); if (sem_init(&sem_[0], 0, 0) != 0) { LOG_ECMA(ERROR) << "sem_[0] init failed"; } if (sem_init(&sem_[1], 0, 0) != 0) { LOG_ECMA(ERROR) << "sem_[1] init failed"; } } CpuProfiler *CpuProfiler::GetInstance() { CpuProfiler *temp = singleton_; if (temp == nullptr) { os::memory::LockHolder lock(synchronizationMutex_); if ((temp = CpuProfiler::singleton_) == nullptr) { CpuProfiler::singleton_ = temp = new CpuProfiler(); } } return CpuProfiler::singleton_; } void CpuProfiler::StartCpuProfilerForInfo(const EcmaVM *vm) { if (isProfiling_) { return; } isProfiling_ = true; #if ECMASCRIPT_ENABLE_ACTIVE_CPUPROFILER #else struct sigaction sa; sa.sa_handler = &GetStackSignalHandler; if (sigemptyset(&sa.sa_mask) != 0) { LOG_ECMA(ERROR) << "Parameter set signal set initialization and emptying failed"; isProfiling_ = false; return; } sa.sa_flags = SA_RESTART; if (sigaction(SIGINT, &sa, nullptr) != 0) { LOG_ECMA(ERROR) << "sigaction failed to set signal"; isProfiling_ = false; return; } #endif tid_ = static_cast(syscall(SYS_gettid)); SamplingProcessor::SetIsStart(true); Taskpool::GetCurrentTaskpool()->PostTask( std::make_unique(generator_, vm, interval_, outToFile_)); } void CpuProfiler::StartCpuProfilerForFile(const EcmaVM *vm, const std::string &fileName) { if (isProfiling_) { return; } isProfiling_ = true; std::string absoluteFilePath(""); if (!CheckFileName(fileName, absoluteFilePath)) { LOG_ECMA(ERROR) << "The fileName contains illegal characters"; isProfiling_ = false; return; } fileName_ = absoluteFilePath; if (fileName_.empty()) { fileName_ = GetProfileName(); } generator_->SetFileName(fileName_); generator_->fileHandle_.open(fileName_.c_str()); if (generator_->fileHandle_.fail()) { LOG_ECMA(ERROR) << "File open failed"; isProfiling_ = false; return; } #if ECMASCRIPT_ENABLE_ACTIVE_CPUPROFILER #else struct sigaction sa; sa.sa_handler = &GetStackSignalHandler; if (sigemptyset(&sa.sa_mask) != 0) { LOG_ECMA(ERROR) << "Parameter set signal set initialization and emptying failed"; isProfiling_ = false; return; } sa.sa_flags = SA_RESTART; if (sigaction(SIGINT, &sa, nullptr) != 0) { LOG_ECMA(ERROR) << "sigaction failed to set signal"; isProfiling_ = false; return; } #endif uint64_t ts = SamplingProcessor::GetMicrosecondsTimeStamp(); ts = ts % TIME_CHANGE; SetProfileStart(ts); outToFile_ = true; SamplingProcessor::SetIsStart(true); Taskpool::GetCurrentTaskpool()->PostTask( std::make_unique(generator_, vm, interval_, outToFile_)); } std::unique_ptr CpuProfiler::StopCpuProfilerForInfo() { std::unique_ptr profileInfo; if (!isProfiling_) { LOG_ECMA(ERROR) << "Do not execute stop cpuprofiler twice in a row or didn't execute the start\ or the sampling thread is not started"; return profileInfo; } if (outToFile_) { LOG_ECMA(ERROR) << "Can not Stop a CpuProfiler sampling which is for file output by this stop method"; return profileInfo; } if (static_cast(tid_) != syscall(SYS_gettid)) { LOG_ECMA(ERROR) << "Thread attempted to close other sampling threads"; return profileInfo; } isProfiling_ = false; SamplingProcessor::SetIsStart(false); generator_->SetLastSampleFlag(); if (sem_post(&sem_[0]) != 0) { LOG_ECMA(ERROR) << "sem_[0] post failed"; return profileInfo; } if (sem_wait(&sem_[1]) != 0) { LOG_ECMA(ERROR) << "sem_[1] wait failed"; return profileInfo; } profileInfo = generator_->GetProfileInfo(); if (singleton_ != nullptr) { delete singleton_; singleton_ = nullptr; } return profileInfo; } void CpuProfiler::SetCpuSamplingInterval(int interval) { interval_ = interval; } void CpuProfiler::StopCpuProfilerForFile() { if (!isProfiling_) { LOG_ECMA(ERROR) << "Do not execute stop cpuprofiler twice in a row or didn't execute the start\ or the sampling thread is not started"; return; } if (!outToFile_) { LOG_ECMA(ERROR) << "Can not Stop a CpuProfiler sampling which is for return profile info by\ this stop method"; return; } if (static_cast(tid_) != syscall(SYS_gettid)) { LOG_ECMA(ERROR) << "Thread attempted to close other sampling threads"; return; } isProfiling_ = false; SamplingProcessor::SetIsStart(false); generator_->SetLastSampleFlag(); if (sem_post(&sem_[0]) != 0) { LOG_ECMA(ERROR) << "sem_[0] post failed"; return; } if (sem_wait(&sem_[1]) != 0) { LOG_ECMA(ERROR) << "sem_[1] wait failed"; return; } generator_->WriteMethodsAndSampleInfo(true); std::string fileData = generator_->GetSampleData(); fileData.replace(fileData.size() - 2, 1, "]"); // 2: Subscript with comma at end of string generator_->fileHandle_ << fileData; if (singleton_ != nullptr) { delete singleton_; singleton_ = nullptr; } } CpuProfiler::~CpuProfiler() { if (sem_destroy(&sem_[0]) != 0) { LOG_ECMA(ERROR) << "sem_[0] destroy failed"; } if (sem_destroy(&sem_[1]) != 0) { LOG_ECMA(ERROR) << "sem_[1] destroy failed"; } if (generator_ != nullptr) { delete generator_; generator_ = nullptr; } } void CpuProfiler::SetProfileStart(uint64_t nowTimeStamp) { uint64_t ts = SamplingProcessor::GetMicrosecondsTimeStamp(); ts = ts % TIME_CHANGE; struct CurrentProcessInfo currentProcessInfo = {0}; GetCurrentProcessInfo(currentProcessInfo); std::string data = ""; data = "[{\"args\":{\"data\":{\"frames\":[{\"processId\":" + std::to_string(currentProcessInfo.pid) + "}]" + ",\"persistentIds\":true}},\"cat\":\"disabled-by-default-devtools.timeline\"," + "\"name\":\"TracingStartedInBrowser\",\"ph\":\"I\",\"pid\":" + std::to_string(currentProcessInfo.pid) + ",\"s\":\"t\",\"tid\":" + std::to_string(currentProcessInfo.tid) + ",\"ts\":" + std::to_string(ts) + ",\"tts\":178460227},\n"; ts = SamplingProcessor::GetMicrosecondsTimeStamp(); ts = ts % TIME_CHANGE; data += "{\"args\":{\"data\":{\"startTime\":" + std::to_string(nowTimeStamp) + "}}," + "\"cat\":\"disabled-by-default-ark.cpu_profiler\",\"id\":\"0x2\"," + "\"name\":\"Profile\",\"ph\":\"P\",\"pid\":" + std::to_string(currentProcessInfo.pid) + ",\"tid\":" + std::to_string(currentProcessInfo.tid) + ",\"ts\":" + std::to_string(ts) + ",\"tts\":" + std::to_string(currentProcessInfo.tts) + "},\n"; generator_->SetStartsampleData(data); } void CpuProfiler::GetCurrentProcessInfo(struct CurrentProcessInfo ¤tProcessInfo) { currentProcessInfo.nowTimeStamp = SamplingProcessor::GetMicrosecondsTimeStamp() % TIME_CHANGE; currentProcessInfo.pid = getpid(); if (syscall(SYS_gettid) == -1) { LOG_FULL(FATAL) << "syscall failed"; UNREACHABLE(); } tid_ = currentProcessInfo.tid = static_cast(syscall(SYS_gettid)); struct timespec time = {0, 0}; clock_gettime(CLOCK_MONOTONIC, &time); currentProcessInfo.tts = static_cast(time.tv_nsec) / 1000; // 1000:Nanoseconds to milliseconds. } void CpuProfiler::GetFrameStack(JSThread *thread) { staticFrameStack_.clear(); SamplesRecord::staticGcState_ = thread->GetGcState(); if (!SamplesRecord::staticGcState_) { FrameHandler frameHandler(thread); for (; frameHandler.HasFrame(); frameHandler.PrevInterpretedFrame()) { if (!frameHandler.IsInterpretedFrame()) { continue; } auto method = frameHandler.CheckAndGetMethod(); if (method != nullptr && staticStackInfo_.count(method) == 0) { ParseMethodInfo(method, frameHandler); } staticFrameStack_.push_back(method); } } } void CpuProfiler::ParseMethodInfo(JSMethod *method, FrameHandler frameHandler) { struct FrameInfo codeEntry; if (method != nullptr && method->IsNativeWithCallField()) { codeEntry.codeType = "other"; auto addr = method->GetNativePointer(); std::stringstream strm; strm << addr; codeEntry.functionName = "native(" + strm.str() + ")"; staticStackInfo_.insert(std::make_pair(method, codeEntry)); } else if (method != nullptr) { codeEntry.codeType = "JS"; const std::string &functionName = method->ParseFunctionName(); if (functionName.empty()) { codeEntry.functionName = "anonymous"; } else { codeEntry.functionName = functionName; } // source file tooling::JSPtExtractor *debugExtractor = JSPandaFileManager::GetInstance()->GetJSPtExtractor(method->GetJSPandaFile()); const std::string &sourceFile = debugExtractor->GetSourceFile(method->GetMethodId()); if (sourceFile.empty()) { codeEntry.url = ""; } else { codeEntry.url = sourceFile; auto iter = scriptIdMap_.find(codeEntry.url); if (iter == scriptIdMap_.end()) { scriptIdMap_.insert(std::make_pair(codeEntry.url, scriptIdMap_.size() + 1)); codeEntry.scriptId = static_cast(scriptIdMap_.size()); } else { codeEntry.scriptId = iter->second; } } // line number int32_t lineNumber = 0; int32_t columnNumber = 0; auto callbackLineFunc = [&](int32_t line) -> bool { lineNumber = line + 1; return true; }; auto callbackColumnFunc = [&](int32_t column) -> bool { columnNumber = column + 1; return true; }; panda_file::File::EntityId methodId = method->GetMethodId(); uint32_t offset = frameHandler.GetBytecodeOffset(); if (!debugExtractor->MatchLineWithOffset(callbackLineFunc, methodId, offset) || !debugExtractor->MatchColumnWithOffset(callbackColumnFunc, methodId, offset)) { codeEntry.lineNumber = 0; codeEntry.columnNumber = 0; } else { codeEntry.lineNumber = lineNumber; codeEntry.columnNumber = columnNumber; } staticStackInfo_.insert(std::make_pair(method, codeEntry)); } } void CpuProfiler::IsNeedAndGetStack(JSThread *thread) { if (thread->GetStackSignal()) { GetFrameStack(thread); if (sem_post(&sem_[0]) != 0) { LOG_ECMA(ERROR) << "sem_[0] post failed"; return; } thread->SetGetStackSignal(false); } } void CpuProfiler::GetStackSignalHandler([[maybe_unused]] int signal) { JSThread *thread = SamplingProcessor::GetJSThread(); GetFrameStack(thread); if (sem_post(&sem_[0]) != 0) { LOG_ECMA(ERROR) << "sem_[0] post failed"; return; } } std::string CpuProfiler::GetProfileName() const { char time1[16] = {0}; // 16:Time format length char time2[16] = {0}; // 16:Time format length time_t timep = std::time(nullptr); struct tm nowTime1; localtime_r(&timep, &nowTime1); size_t result = 0; result = strftime(time1, sizeof(time1), "%Y%m%d", &nowTime1); if (result == 0) { LOG_ECMA(ERROR) << "get time failed"; return ""; } result = strftime(time2, sizeof(time2), "%H%M%S", &nowTime1); if (result == 0) { LOG_ECMA(ERROR) << "get time failed"; return ""; } std::string profileName = "cpuprofile-"; profileName += time1; profileName += "TO"; profileName += time2; profileName += ".json"; return profileName; } bool CpuProfiler::CheckFileName(const std::string &fileName, std::string &absoluteFilePath) const { if (fileName.empty()) { return true; } if (fileName.size() > PATH_MAX) { return false; } CVector resolvedPath(PATH_MAX); auto result = realpath(fileName.c_str(), resolvedPath.data()); if (result == nullptr) { LOG_ECMA(INFO) << "The file path does not exist"; } std::ofstream file(resolvedPath.data()); if (!file.good()) { return false; } file.close(); absoluteFilePath = resolvedPath.data(); return true; } } // namespace panda::ecmascript