1 /*
2 * Copyright (c) 2024 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 #include "gtest/gtest.h"
16
17 #include <future>
18
19 #include "ecmascript/jspandafile/js_pandafile_manager.h"
20 #include "ecmascript/napi/include/jsnapi.h"
21 #include "ecmascript/pgo_profiler/pgo_profiler_decoder.h"
22 #include "ecmascript/pgo_profiler/pgo_profiler_manager.h"
23 #include "ecmascript/pgo_profiler/pgo_utils.h"
24 #include "ecmascript/tests/test_helper.h"
25
26 using namespace panda;
27 using namespace panda::ecmascript;
28 using namespace panda::ecmascript::pgo;
29 using namespace panda::panda_file;
30
31 namespace panda::test {
32 class PGOProfilerMock : public PGOProfiler {
33 public:
PGOProfilerMock(EcmaVM * vm,bool isEnable)34 PGOProfilerMock(EcmaVM* vm, bool isEnable): PGOProfiler(vm, isEnable) {}
DispatchDumpTask()35 void DispatchDumpTask()
36 {
37 LOG_PGO(INFO) << "dispatch dump task";
38 }
39 };
40
41 class PGOStateMock : public PGOState {
42 public:
PGOStateMock()43 PGOStateMock(): PGOState() {}
WaitDumpTest()44 void WaitDumpTest()
45 {
46 LockHolder lock(stateMutex_);
47 WaitDump();
48 }
49 };
50
51 class PGOProfilerTestOne : public testing::Test {
52 public:
SetUpTestSuite()53 static void SetUpTestSuite()
54 {
55 GTEST_LOG_(INFO) << "SetUpTestSuite";
56 }
TearDownTestSuite()57 static void TearDownTestSuite()
58 {
59 GTEST_LOG_(INFO) << "TearDownTestSuite";
60 }
SetUp()61 void SetUp() override {}
TearDown()62 void TearDown() override {}
63
64 protected:
ParseRelatedPandaFileMethods(panda::ecmascript::pgo::PGOProfilerDecoder & loader,std::unordered_map<std::string,std::unordered_map<std::string,std::vector<PGOMethodId>>> & methodIdInAp)65 void ParseRelatedPandaFileMethods(
66 panda::ecmascript::pgo::PGOProfilerDecoder& loader,
67 std::unordered_map<std::string, std::unordered_map<std::string, std::vector<PGOMethodId>>>& methodIdInAp)
68 {
69 std::shared_ptr<PGOAbcFilePool> abcFilePool = std::make_shared<PGOAbcFilePool>();
70 ASSERT_TRUE(loader.LoadFull(abcFilePool));
71 for (const auto& recordInfo: loader.GetRecordDetailInfos().GetRecordInfos()) {
72 auto recordProfile = recordInfo.first;
73 ASSERT_EQ(recordProfile.GetKind(), ProfileType::Kind::RecordClassId);
74 if (recordProfile.IsNone()) {
75 continue;
76 }
77 LOG_ECMA(ERROR) << "recordProfile: " << recordProfile.GetTypeString();
78 const auto* recordName = loader.GetRecordDetailInfos().GetRecordPool()->GetName(recordProfile);
79 ASSERT(recordName != nullptr);
80 const auto abcNormalizedDesc =
81 JSPandaFile::GetNormalizedFileDesc(abcFilePool->GetEntry(recordProfile.GetAbcId())->GetData());
82 if (abcNormalizedDesc.empty()) {
83 continue;
84 }
85
86 const auto* info = recordInfo.second;
87 for (const auto& method: info->GetMethodInfos()) {
88 // add ap entry info
89 methodIdInAp[abcNormalizedDesc.c_str()][recordName].emplace_back(method.first);
90 }
91 };
92 }
93
CheckApMethods(std::unordered_map<std::string,std::unordered_map<std::string,std::vector<PGOMethodId>>> & methodIdInAp)94 void CheckApMethods(
95 std::unordered_map<std::string, std::unordered_map<std::string, std::vector<PGOMethodId>>>& methodIdInAp)
96 {
97 for (auto abcIter = methodIdInAp.begin(); abcIter != methodIdInAp.end();) {
98 std::string fileName(abcIter->first.c_str());
99 auto lastDirToken = fileName.find_last_of('/');
100 if (lastDirToken != std::string::npos) {
101 fileName = fileName.substr(lastDirToken + 1);
102 }
103 std::unordered_map<std::string, std::vector<PGOMethodId>>& recordMethodList = abcIter->second;
104 CheckApMethodsInApFiles(fileName, recordMethodList);
105 if (recordMethodList.empty()) {
106 abcIter = methodIdInAp.erase(abcIter);
107 } else {
108 abcIter++;
109 }
110 }
111 ASSERT_TRUE(methodIdInAp.empty());
112 }
113
CreateJSPandaFile(const std::string & filename,std::vector<MethodLiteral * > & methodLiterals)114 std::shared_ptr<JSPandaFile> CreateJSPandaFile(const std::string& filename,
115 std::vector<MethodLiteral*>& methodLiterals)
116 {
117 std::string targetAbcPath = std::string(TARGET_ABC_PATH) + filename.c_str();
118 auto pfPtr = panda_file::OpenPandaFileOrZip(targetAbcPath, panda_file::File::READ_WRITE);
119 JSPandaFileManager* pfManager = JSPandaFileManager::GetInstance();
120 auto pf = pfManager->NewJSPandaFile(pfPtr.release(), filename.c_str());
121
122 const File* file = pf->GetPandaFile();
123 auto classes = pf->GetClasses();
124
125 for (size_t i = 0; i < classes.Size(); i++) {
126 panda_file::File::EntityId classId(classes[i]);
127 if (!classId.IsValid() || pf->IsExternal(classId)) {
128 continue;
129 }
130 ClassDataAccessor cda(*file, classId);
131 cda.EnumerateMethods([pf, &methodLiterals](panda_file::MethodDataAccessor& mda) {
132 auto* methodLiteral = new MethodLiteral(mda.GetMethodId());
133 methodLiteral->Initialize(pf.get());
134 pf->SetMethodLiteralToMap(methodLiteral);
135 methodLiterals.push_back(methodLiteral);
136 });
137 }
138 return pf;
139 }
140
CheckApMethodsInApFiles(const std::string & fileName,std::unordered_map<std::string,std::vector<PGOMethodId>> & recordMethodList)141 void CheckApMethodsInApFiles(const std::string& fileName,
142 std::unordered_map<std::string, std::vector<PGOMethodId>>& recordMethodList)
143 {
144 std::vector<MethodLiteral*> methodLiterals {};
145 auto pf = CreateJSPandaFile(fileName, methodLiterals);
146
147 for (auto& methodLiteral: methodLiterals) {
148 auto methodName = MethodLiteral::GetRecordName(pf.get(), methodLiteral->GetMethodId());
149 auto recordEntry = recordMethodList.find(methodName.c_str());
150 if (recordEntry == recordMethodList.end()) {
151 continue;
152 }
153 for (size_t index = 0; index < recordEntry->second.size(); ++index) {
154 if (!(recordEntry->second.at(index) == methodLiteral->GetMethodId())) {
155 continue;
156 }
157 recordEntry->second.erase(recordEntry->second.begin() + index);
158 if (recordEntry->second.empty()) {
159 recordEntry = recordMethodList.erase(recordEntry);
160 }
161 break;
162 }
163 }
164 }
165
166 static constexpr uint32_t threshhold = 0;
167 };
168
HWTEST_F_L0(PGOProfilerTestOne,WithWorker)169 HWTEST_F_L0(PGOProfilerTestOne, WithWorker)
170 {
171 mkdir("ark-profiler-worker/", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
172 RuntimeOption option;
173 option.SetEnableProfile(true);
174 option.SetLogLevel(LOG_LEVEL::INFO);
175 option.SetProfileDir("ark-profiler-worker/");
176 EcmaVM* vm = JSNApi::CreateJSVM(option);
177 ASSERT_TRUE(vm != nullptr) << "Cannot create Runtime";
178 std::string targetAbcPath = std::string(TARGET_ABC_PATH) + "truck.abc";
179 auto result = JSNApi::Execute(vm, targetAbcPath, "truck", false);
180 EXPECT_TRUE(result);
181
182 std::thread workerThread([vm]() {
183 RuntimeOption workerOption;
184 workerOption.SetEnableProfile(true);
185 workerOption.SetLogLevel(LOG_LEVEL::INFO);
186 workerOption.SetProfileDir("ark-profiler-worker/");
187 workerOption.SetIsWorker();
188 EcmaVM* workerVm = JSNApi::CreateJSVM(workerOption);
189 ASSERT_TRUE(workerVm != nullptr) << "Cannot create Worker Runtime";
190 JSNApi::AddWorker(vm, workerVm);
191 std::string targetAbcPath = std::string(TARGET_ABC_PATH) + "call_test.abc";
192 auto result = JSNApi::Execute(workerVm, targetAbcPath, "call_test", false);
193 EXPECT_TRUE(result);
194 auto hasDeleted = JSNApi::DeleteWorker(vm, workerVm);
195 EXPECT_TRUE(hasDeleted);
196 JSNApi::DestroyJSVM(workerVm);
197 });
198 workerThread.join();
199
200 JSNApi::DestroyJSVM(vm);
201
202 vm = JSNApi::CreateJSVM(option);
203 PGOProfilerDecoder loader("ark-profiler-worker/modules.ap", threshhold);
204 std::unordered_map<std::string, std::unordered_map<std::string, std::vector<PGOMethodId>>> methodIdInAp;
205 ParseRelatedPandaFileMethods(loader, methodIdInAp);
206 ASSERT_EQ(methodIdInAp.size(), 3);
207 CheckApMethods(methodIdInAp);
208 JSNApi::DestroyJSVM(vm);
209 unlink("ark-profiler-worker/modules.ap");
210 rmdir("ark-profiler-worker/");
211 }
212
HWTEST_F_L0(PGOProfilerTestOne,ForceDump)213 HWTEST_F_L0(PGOProfilerTestOne, ForceDump)
214 {
215 mkdir("ark-profiler-worker/", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
216 RuntimeOption option;
217 option.SetEnableProfile(true);
218 option.SetLogLevel(LOG_LEVEL::INFO);
219 option.SetProfileDir("ark-profiler-worker/");
220 EcmaVM* vm = JSNApi::CreateJSVM(option);
221 ASSERT_TRUE(vm != nullptr) << "Cannot create Runtime";
222
223 std::string targetAbcPath = std::string(TARGET_ABC_PATH) + "truck.abc";
224 auto result = JSNApi::Execute(vm, targetAbcPath, "truck", false);
225 EXPECT_TRUE(result);
226
227 std::thread workerThread([vm]() {
228 RuntimeOption workerOption;
229 workerOption.SetEnableProfile(true);
230 workerOption.SetLogLevel(LOG_LEVEL::INFO);
231 workerOption.SetProfileDir("ark-profiler-worker/");
232 workerOption.SetIsWorker();
233 EcmaVM* workerVm = JSNApi::CreateJSVM(workerOption);
234 ASSERT_TRUE(workerVm != nullptr) << "Cannot create Worker Runtime";
235
236 JSNApi::AddWorker(vm, workerVm);
237
238 std::string targetAbcPath = std::string(TARGET_ABC_PATH) + "call_test.abc";
239 auto result = JSNApi::Execute(workerVm, targetAbcPath, "call_test", false);
240 EXPECT_TRUE(result);
241
242 auto hasDeleted = JSNApi::DeleteWorker(vm, workerVm);
243 EXPECT_TRUE(hasDeleted);
244 JSNApi::DestroyJSVM(workerVm);
245 });
246
247 auto manager = PGOProfilerManager::GetInstance();
248 manager->SetForceDump(true);
249 manager->TryDispatchDumpTask(nullptr);
250 while (manager->IsTaskRunning()) {
251 std::this_thread::sleep_for(std::chrono::milliseconds(100));
252 }
253
254 PGOProfilerDecoder loader("ark-profiler-worker/modules.ap", threshhold);
255 std::shared_ptr<PGOAbcFilePool> abcFilePool = std::make_shared<PGOAbcFilePool>();
256 ASSERT_TRUE(loader.LoadFull(abcFilePool));
257
258 workerThread.join();
259 JSNApi::DestroyJSVM(vm);
260 unlink("ark-profiler-worker/modules.ap");
261 rmdir("ark-profiler-worker/");
262 }
263
HWTEST_F_L0(PGOProfilerTestOne,SuspendThenNotify)264 HWTEST_F_L0(PGOProfilerTestOne, SuspendThenNotify)
265 {
266 auto state = std::make_shared<PGOState>();
267 EXPECT_TRUE(state->StateIsStop());
268 state->TryChangeState(PGOState::State::STOP, PGOState::State::START);
269 EXPECT_TRUE(state->StateIsStart());
270
271 std::thread thread1([state]() { state->SuspendByGC(); });
272
273 std::thread thread2([state]() {
274 std::this_thread::sleep_for(std::chrono::milliseconds(500));
275 EXPECT_TRUE(state->GCIsWaiting());
276 state->SetStopAndNotify();
277 });
278
279 thread1.join();
280 thread2.join();
281
282 EXPECT_TRUE(state->GCIsRunning());
283 EXPECT_TRUE(state->StateIsStop());
284 }
285
HWTEST_F_L0(PGOProfilerTestOne,SuspendThenNotifyThenResume)286 HWTEST_F_L0(PGOProfilerTestOne, SuspendThenNotifyThenResume)
287 {
288 auto state = std::make_shared<PGOState>();
289 EXPECT_TRUE(state->StateIsStop());
290 state->TryChangeState(PGOState::State::STOP, PGOState::State::START);
291 EXPECT_TRUE(state->StateIsStart());
292
293 std::thread thread1([state]() {
294 RuntimeOption option;
295 option.SetEnableProfile(true);
296 option.SetLogLevel(LOG_LEVEL::INFO);
297 option.SetProfileDir("ark-profiler-worker/");
298 EcmaVM* vm = JSNApi::CreateJSVM(option);
299 auto profiler = std::make_shared<PGOProfilerMock>(vm, true);
300 state->SuspendByGC();
301 state->ResumeByGC(profiler.get());
302 JSNApi::DestroyJSVM(vm);
303 });
304
305 std::thread thread2([state]() {
306 std::this_thread::sleep_for(std::chrono::milliseconds(500));
307 EXPECT_TRUE(state->GCIsWaiting());
308 state->SetStopAndNotify();
309 });
310
311 thread1.join();
312 thread2.join();
313
314 EXPECT_TRUE(state->GCIsStop());
315 EXPECT_TRUE(state->StateIsStop());
316 }
317
HWTEST_F_L0(PGOProfilerTestOne,StopSuspend)318 HWTEST_F_L0(PGOProfilerTestOne, StopSuspend)
319 {
320 auto state = std::make_shared<PGOState>();
321 EXPECT_TRUE(state->StateIsStop());
322 state->TryChangeState(PGOState::State::STOP, PGOState::State::START);
323 EXPECT_TRUE(state->StateIsStart());
324
325 std::thread thread1([state]() { state->SetStopAndNotify(); });
326
327 std::thread thread2([state]() {
328 std::this_thread::sleep_for(std::chrono::milliseconds(500));
329 state->SuspendByGC();
330 });
331
332 thread1.join();
333 thread2.join();
334
335 EXPECT_TRUE(state->GCIsRunning());
336 EXPECT_TRUE(state->StateIsStop());
337 }
338
HWTEST_F_L0(PGOProfilerTestOne,SuspendOrNotify)339 HWTEST_F_L0(PGOProfilerTestOne, SuspendOrNotify)
340 {
341 auto state = std::make_shared<PGOState>();
342 EXPECT_TRUE(state->StateIsStop());
343 state->TryChangeState(PGOState::State::STOP, PGOState::State::START);
344 EXPECT_TRUE(state->StateIsStart());
345
346 std::mutex mtx;
347 std::condition_variable cv;
348 bool ready = false;
349
350 std::thread thread1([state, &mtx, &cv, &ready]() {
351 {
352 std::unique_lock<std::mutex> lock(mtx);
353 cv.wait(lock, [&ready]() { return ready; });
354 }
355 state->SuspendByGC();
356 });
357
358 std::thread thread2([state, &mtx, &cv, &ready]() {
359 {
360 std::unique_lock<std::mutex> lock(mtx);
361 cv.wait(lock, [&ready]() { return ready; });
362 }
363 state->SetStopAndNotify();
364 });
365
366 {
367 std::lock_guard<std::mutex> lock(mtx);
368 ready = true;
369 }
370 cv.notify_all();
371 thread1.join();
372 thread2.join();
373
374 EXPECT_TRUE(state->GCIsWaiting() || state->GCIsRunning());
375 EXPECT_TRUE(state->StateIsStop());
376 }
377
HWTEST_F_L0(PGOProfilerTestOne,WaitDumpThenNotifyAll)378 HWTEST_F_L0(PGOProfilerTestOne, WaitDumpThenNotifyAll)
379 {
380 auto state = std::make_shared<PGOStateMock>();
381 EXPECT_TRUE(state->StateIsStop());
382 state->TryChangeState(PGOState::State::STOP, PGOState::State::START);
383 EXPECT_TRUE(state->StateIsStart());
384
385 std::mutex mtx;
386 std::condition_variable cv;
387 bool ready = false;
388
389 std::future<void> future1 = std::async(std::launch::async, [state, &mtx, &cv, &ready]() {
390 {
391 std::unique_lock<std::mutex> lock(mtx);
392 cv.wait(lock, [&ready]() { return ready; });
393 }
394 state->WaitDumpTest();
395 });
396
397 std::future<void> future2 = std::async(std::launch::async, [state, &mtx, &cv, &ready]() {
398 {
399 std::unique_lock<std::mutex> lock(mtx);
400 cv.wait(lock, [&ready]() { return ready; });
401 }
402 state->WaitDumpTest();
403 });
404
405 {
406 std::lock_guard<std::mutex> lock(mtx);
407 ready = true;
408 }
409 cv.notify_all();
410 std::this_thread::sleep_for(std::chrono::milliseconds(500));
411 state->SetStopAndNotify();
412
413 auto status1 = future1.wait_for(std::chrono::seconds(2));
414 auto status2 = future2.wait_for(std::chrono::seconds(2));
415 ASSERT_NE(status1, std::future_status::timeout) << "thread 1 may freeze";
416 ASSERT_NE(status2, std::future_status::timeout) << "thread 2 may freeze";
417 }
418
HWTEST_F_L0(PGOProfilerTestOne,SetSaveAndNotify)419 HWTEST_F_L0(PGOProfilerTestOne, SetSaveAndNotify)
420 {
421 auto state = std::make_shared<PGOStateMock>();
422 EXPECT_TRUE(state->StateIsStop());
423 state->TryChangeState(PGOState::State::STOP, PGOState::State::START);
424 EXPECT_TRUE(state->StateIsStart());
425
426 std::mutex mtx;
427 std::condition_variable cv;
428 bool ready = false;
429
430 std::future<void> future1 = std::async(std::launch::async, [state, &mtx, &cv, &ready]() {
431 {
432 std::unique_lock<std::mutex> lock(mtx);
433 cv.wait(lock, [&ready]() { return ready; });
434 }
435 state->WaitDumpTest();
436 });
437
438 std::future<void> future2 = std::async(std::launch::async, [state, &mtx, &cv, &ready]() {
439 {
440 std::unique_lock<std::mutex> lock(mtx);
441 cv.wait(lock, [&ready]() { return ready; });
442 }
443 state->WaitDumpTest();
444 });
445
446 {
447 std::lock_guard<std::mutex> lock(mtx);
448 ready = true;
449 }
450 cv.notify_all();
451 std::this_thread::sleep_for(std::chrono::milliseconds(500));
452 state->SetSaveAndNotify();
453
454 auto status1 = future1.wait_for(std::chrono::seconds(2));
455 auto status2 = future2.wait_for(std::chrono::seconds(2));
456 ASSERT_NE(status1, std::future_status::timeout) << "thread 1 may freeze";
457 ASSERT_NE(status2, std::future_status::timeout) << "thread 2 may freeze";
458
459 EXPECT_TRUE(state->StateIsSave());
460 }
461 } // namespace panda::test
462