• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "ecmascript/cpu_profiler/cpu_profiler.h"
17 #include <atomic>
18 #include <chrono>
19 #include <climits>
20 #include <csignal>
21 #include <fstream>
22 #include "ecmascript/jspandafile/js_pandafile_manager.h"
23 #include "ecmascript/platform/platform.h"
24 
25 namespace panda::ecmascript {
26 CMap<JSMethod *, struct StackInfo> CpuProfiler::staticStackInfo_ = CMap<JSMethod *, struct StackInfo>();
27 std::atomic<CpuProfiler*> CpuProfiler::singleton_ = nullptr;
28 sem_t CpuProfiler::sem_ = sem_t {};
29 CMap<std::string, int> CpuProfiler::scriptIdMap_ = CMap<std::string, int>();
30 CVector<JSMethod *> CpuProfiler::staticFrameStack_ = CVector<JSMethod *>();
31 os::memory::Mutex CpuProfiler::synchronizationMutex_;
CpuProfiler()32 CpuProfiler::CpuProfiler()
33 {
34     generator_ = new ProfileGenerator();
35     if (sem_init(&sem_, 0, 1) != 0) {
36         LOG(ERROR, RUNTIME) << "sem_ init failed";
37     }
38     if (sem_wait(&sem_) != 0) {
39         LOG(ERROR, RUNTIME) << "sem_ wait failed";
40     }
41 }
42 
GetInstance()43 CpuProfiler *CpuProfiler::GetInstance()
44 {
45     CpuProfiler *temp = singleton_;
46     if (temp == nullptr) {
47         os::memory::LockHolder lock(synchronizationMutex_);
48         if ((temp = CpuProfiler::singleton_) == nullptr) {
49             CpuProfiler::singleton_ = temp = new CpuProfiler();
50         }
51     }
52     return CpuProfiler::singleton_;
53 }
54 
StartCpuProfiler(const EcmaVM * vm,const std::string & fileName)55 void CpuProfiler::StartCpuProfiler(const EcmaVM *vm, const std::string &fileName)
56 {
57     if (isOnly_) {
58         return;
59     }
60     isOnly_ = true;
61     std::string absoluteFilePath("");
62     if (!CheckFileName(fileName, absoluteFilePath)) {
63         LOG(ERROR, RUNTIME) << "The fileName contains illegal characters";
64         isOnly_ = false;
65         return;
66     }
67     fileName_ = absoluteFilePath;
68     if (fileName_.empty()) {
69         fileName_ = GetProfileName();
70     }
71     generator_->SetFileName(fileName_);
72     generator_->fileHandle_.open(fileName_.c_str());
73     if (generator_->fileHandle_.fail()) {
74         LOG(ERROR, RUNTIME) << "File open failed";
75         isOnly_ = false;
76         return;
77     }
78 #if ECMASCRIPT_ENABLE_ACTIVE_CPUPROFILER
79 #else
80     struct sigaction sa;
81     sa.sa_handler = &GetStackSignalHandler;
82     if (sigemptyset(&sa.sa_mask) != 0) {
83         LOG(ERROR, RUNTIME) << "Parameter set signal set initialization and emptying failed";
84         isOnly_ = false;
85         return;
86     }
87     sa.sa_flags = SA_RESTART;
88     if (sigaction(SIGINT, &sa, nullptr) != 0) {
89         LOG(ERROR, RUNTIME) << "sigaction failed to set signal";
90         isOnly_ = false;
91         return;
92     }
93 #endif
94     uint64_t ts = ProfileProcessor::GetMicrosecondsTimeStamp();
95     ts = ts % TIME_CHANGE;
96     SetProfileStart(ts);
97     Platform::GetCurrentPlatform()->PostTask(std::make_unique<ProfileProcessor>(generator_, vm, interval_));
98 }
99 
StopCpuProfiler()100 void CpuProfiler::StopCpuProfiler()
101 {
102     if (!isOnly_) {
103         LOG(ERROR, RUNTIME) << "Do not execute stop cpuprofiler twice in a row or didn't execute the start\
104                                 or the sampling thread is not started";
105         return;
106     }
107     if (static_cast<long>(tid_) != syscall(SYS_gettid)) {
108         LOG(ERROR, RUNTIME) << "Thread attempted to close other sampling threads";
109         return;
110     }
111     isOnly_ = false;
112     ProfileProcessor::SetIsStart(false);
113     if (sem_wait(&sem_) != 0) {
114         LOG(ERROR, RUNTIME) << "sem_ wait failed";
115         return;
116     }
117     generator_->WriteMethodsAndSampleInfo(true);
118     generator_->fileHandle_ << generator_->GetSampleData();
119     if (singleton_ != nullptr) {
120         delete singleton_;
121         singleton_ = nullptr;
122     }
123 }
124 
~CpuProfiler()125 CpuProfiler::~CpuProfiler()
126 {
127     if (sem_destroy(&sem_) != 0) {
128         LOG(ERROR, RUNTIME) << "sem_ destroy failed";
129     }
130     if (generator_ != nullptr) {
131         delete generator_;
132         generator_ = nullptr;
133     }
134 }
135 
SetProfileStart(uint64_t nowTimeStamp)136 void CpuProfiler::SetProfileStart(uint64_t nowTimeStamp)
137 {
138     uint64_t ts = ProfileProcessor::GetMicrosecondsTimeStamp();
139     ts = ts % TIME_CHANGE;
140     struct CurrentProcessInfo currentProcessInfo = {0};
141     GetCurrentProcessInfo(currentProcessInfo);
142     std::string data = "";
143     data = "[{\"args\":{\"data\":{\"frames\":[{\"processId\":" + std::to_string(currentProcessInfo.pid) + "}]"
144             + ",\"persistentIds\":true}},\"cat\":\"disabled-by-default-devtools.timeline\","
145             + "\"name\":\"TracingStartedInBrowser\",\"ph\":\"I\",\"pid\":"
146             + std::to_string(currentProcessInfo.pid) + ",\"s\":\"t\",\"tid\":"
147             + std::to_string(currentProcessInfo.tid) + ",\"ts\":"
148             + std::to_string(ts) + ",\"tts\":178460227},\n";
149     ts = ProfileProcessor::GetMicrosecondsTimeStamp();
150     ts = ts % TIME_CHANGE;
151     data += "{\"args\":{\"data\":{\"startTime\":" + std::to_string(nowTimeStamp) + "}},"
152             + "\"cat\":\"disabled-by-default-ark.cpu_profiler\",\"id\":\"0x2\","
153             + "\"name\":\"Profile\",\"ph\":\"P\",\"pid\":"
154             + std::to_string(currentProcessInfo.pid) + ",\"tid\":"
155             + std::to_string(currentProcessInfo.tid) + ",\"ts\":"
156             + std::to_string(ts) + ",\"tts\":" + std::to_string(currentProcessInfo.tts)
157             + "},\n";
158     generator_->SetStartsampleData(data);
159 }
160 
GetCurrentProcessInfo(struct CurrentProcessInfo & currentProcessInfo)161 void CpuProfiler::GetCurrentProcessInfo(struct CurrentProcessInfo &currentProcessInfo)
162 {
163     currentProcessInfo.nowTimeStamp = ProfileProcessor::GetMicrosecondsTimeStamp() % TIME_CHANGE;
164     currentProcessInfo.pid = getpid();
165     tid_ = currentProcessInfo.tid = syscall(SYS_gettid);
166     struct timespec time = {0, 0};
167     clock_gettime(CLOCK_MONOTONIC, &time);
168     currentProcessInfo.tts = time.tv_nsec / 1000; // 1000:Nanoseconds to milliseconds.
169 }
170 
GetFrameStack(JSThread * thread)171 void CpuProfiler::GetFrameStack(JSThread *thread)
172 {
173     staticFrameStack_.clear();
174     ProfileGenerator::staticGcState_ = thread->GetGcState();
175     if (!ProfileGenerator::staticGcState_) {
176         JSTaggedType *sp_ = const_cast<JSTaggedType *>(thread->GetCurrentSPFrame());
177         InterpretedFrameHandler frameHandler(sp_);
178         for (; frameHandler.HasFrame(); frameHandler.PrevInterpretedFrame()) {
179             if (frameHandler.IsBreakFrame()) {
180                 continue;
181             }
182             auto *method = frameHandler.GetMethod();
183             if (method != nullptr && staticStackInfo_.count(method) == 0) {
184                 ParseMethodInfo(method, thread, frameHandler);
185             }
186             staticFrameStack_.push_back(method);
187         }
188     }
189 }
190 
ParseMethodInfo(JSMethod * method,JSThread * thread,InterpretedFrameHandler frameHandler)191 void CpuProfiler::ParseMethodInfo(JSMethod *method, JSThread *thread, InterpretedFrameHandler frameHandler)
192 {
193     struct StackInfo codeEntry;
194     if (method != nullptr && method->IsNative()) {
195         codeEntry.codeType = "other";
196         codeEntry.functionName = "native";
197         staticStackInfo_.insert(std::make_pair(method, codeEntry));
198     } else {
199         codeEntry.codeType = "JS";
200         const std::string &functionName = method->ParseFunctionName();
201         if (functionName.empty()) {
202             codeEntry.functionName = "anonymous";
203         } else {
204             codeEntry.functionName = functionName.c_str();
205         }
206         // source file
207         tooling::JSPtExtractor *debugExtractor =
208             JSPandaFileManager::GetInstance()->GetJSPtExtractor(method->GetJSPandaFile());
209         if (method == nullptr) {
210             return;
211         }
212         const std::string &sourceFile = debugExtractor->GetSourceFile(method->GetFileId());
213         if (sourceFile.empty()) {
214             codeEntry.url = "";
215         } else {
216             codeEntry.url = sourceFile.c_str();
217             auto iter = scriptIdMap_.find(codeEntry.url);
218             if (iter == scriptIdMap_.end()) {
219                 scriptIdMap_.insert(std::make_pair(codeEntry.url, scriptIdMap_.size() + 1));
220                 codeEntry.scriptId = scriptIdMap_.size();
221             } else {
222                 codeEntry.scriptId = iter->second;
223             }
224         }
225         // line number
226         int32_t lineNumber = 0;
227         int32_t columnNumber = 0;
228         auto callbackLineFunc = [&](int32_t line) -> bool {
229             lineNumber = line + 1;
230             return true;
231         };
232         auto callbackColumnFunc = [&](int32_t column) -> bool {
233             columnNumber = column + 1;
234             return true;
235         };
236         panda_file::File::EntityId methodId = method->GetFileId();
237         uint32_t offset = frameHandler.GetBytecodeOffset();
238         if (!debugExtractor->MatchLineWithOffset(callbackLineFunc, methodId, offset) ||
239             !debugExtractor->MatchColumnWithOffset(callbackColumnFunc, methodId, offset)) {
240             codeEntry.lineNumber = 0;
241             codeEntry.columnNumber = 0;
242         } else {
243             codeEntry.lineNumber = lineNumber;
244             codeEntry.columnNumber = columnNumber;
245         }
246         staticStackInfo_.insert(std::make_pair(method, codeEntry));
247     }
248 }
249 
IsNeedAndGetStack(JSThread * thread)250 void CpuProfiler::IsNeedAndGetStack(JSThread *thread)
251 {
252     if (thread->GetStackSignal()) {
253         GetFrameStack(thread);
254         if (sem_post(&CpuProfiler::sem_) != 0) {
255             LOG(ERROR, RUNTIME) << "sem_ post failed";
256             return;
257         }
258         thread->SetGetStackSignal(false);
259     }
260 }
261 
GetStackSignalHandler(int signal)262 void CpuProfiler::GetStackSignalHandler(int signal)
263 {
264     JSThread *thread = ProfileProcessor::GetJSThread();
265     GetFrameStack(thread);
266     if (sem_post(&CpuProfiler::sem_) != 0) {
267         LOG(ERROR, RUNTIME) << "sem_ post failed";
268         return;
269     }
270 }
271 
GetProfileName() const272 std::string CpuProfiler::GetProfileName() const
273 {
274     char time1[16] = {0}; // 16:Time format length
275     char time2[16] = {0}; // 16:Time format length
276     time_t timep = std::time(NULL);
277     struct tm nowTime1;
278     localtime_r(&timep, &nowTime1);
279     size_t result = 0;
280     result = strftime(time1, sizeof(time1), "%Y%m%d", &nowTime1);
281     if (result == 0) {
282         LOG(ERROR, RUNTIME) << "get time failed";
283         return "";
284     }
285     result = strftime(time2, sizeof(time2), "%H%M%S", &nowTime1);
286     if (result == 0) {
287         LOG(ERROR, RUNTIME) << "get time failed";
288         return "";
289     }
290     std::string profileName = "cpuprofile-";
291     profileName += time1;
292     profileName += "TO";
293     profileName += time2;
294     profileName += ".json";
295     return profileName;
296 }
297 
CheckFileName(const std::string & fileName,std::string & absoluteFilePath) const298 bool CpuProfiler::CheckFileName(const std::string &fileName, std::string &absoluteFilePath) const
299 {
300     if (fileName.empty()) {
301         return true;
302     }
303 
304     if (fileName.size() > PATH_MAX) {
305         return false;
306     }
307 
308     CVector<char> resolvedPath(PATH_MAX);
309     auto result = realpath(fileName.c_str(), resolvedPath.data());
310     if (result == nullptr) {
311         LOG(INFO, RUNTIME) << "The file path does not exist";
312     }
313     std::ofstream file(resolvedPath.data());
314     if (!file.good()) {
315         return false;
316     }
317     file.close();
318     absoluteFilePath = resolvedPath.data();
319     return true;
320 }
321 } // namespace panda::ecmascript
322