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 ¤tProcessInfo)
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