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(common::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(common::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(common::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(common::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::mutex mtx;
294 std::condition_variable cv;
295 bool gcWaiting = false;
296
297 std::thread thread1([state, &mtx, &cv, &gcWaiting]() {
298 RuntimeOption option;
299 option.SetEnableProfile(true);
300 option.SetLogLevel(common::LOG_LEVEL::INFO);
301 option.SetProfileDir("ark-profiler-worker/");
302 EcmaVM* vm = JSNApi::CreateJSVM(option);
303 auto profiler = std::make_shared<PGOProfilerMock>(vm, true);
304 {
305 std::lock_guard<std::mutex> lock(mtx);
306 gcWaiting = true;
307 cv.notify_one();
308 }
309 state->SuspendByGC();
310 state->ResumeByGC(profiler.get());
311 profiler.reset();
312 JSNApi::DestroyJSVM(vm);
313 });
314
315 std::thread thread2([state, &mtx, &cv, &gcWaiting]() {
316 {
317 std::unique_lock<std::mutex> lock(mtx);
318 cv.wait(lock, [&gcWaiting]() { return gcWaiting; });
319 }
320 std::this_thread::sleep_for(std::chrono::milliseconds(500));
321 EXPECT_TRUE(state->GCIsWaiting());
322 state->SetStopAndNotify();
323 });
324
325 thread1.join();
326 thread2.join();
327
328 EXPECT_TRUE(state->GCIsStop());
329 EXPECT_TRUE(state->StateIsStop());
330 }
331
HWTEST_F_L0(PGOProfilerTestOne,StopSuspend)332 HWTEST_F_L0(PGOProfilerTestOne, StopSuspend)
333 {
334 auto state = std::make_shared<PGOState>();
335 EXPECT_TRUE(state->StateIsStop());
336 state->TryChangeState(PGOState::State::STOP, PGOState::State::START);
337 EXPECT_TRUE(state->StateIsStart());
338
339 std::thread thread1([state]() { state->SetStopAndNotify(); });
340
341 std::thread thread2([state]() {
342 std::this_thread::sleep_for(std::chrono::milliseconds(500));
343 state->SuspendByGC();
344 });
345
346 thread1.join();
347 thread2.join();
348
349 EXPECT_TRUE(state->GCIsRunning());
350 EXPECT_TRUE(state->StateIsStop());
351 }
352
HWTEST_F_L0(PGOProfilerTestOne,SuspendOrNotify)353 HWTEST_F_L0(PGOProfilerTestOne, SuspendOrNotify)
354 {
355 auto state = std::make_shared<PGOState>();
356 EXPECT_TRUE(state->StateIsStop());
357 state->TryChangeState(PGOState::State::STOP, PGOState::State::START);
358 EXPECT_TRUE(state->StateIsStart());
359
360 std::mutex mtx;
361 std::condition_variable cv;
362 bool ready = false;
363
364 std::thread thread1([state, &mtx, &cv, &ready]() {
365 {
366 std::unique_lock<std::mutex> lock(mtx);
367 cv.wait(lock, [&ready]() { return ready; });
368 }
369 state->SuspendByGC();
370 });
371
372 std::thread thread2([state, &mtx, &cv, &ready]() {
373 {
374 std::unique_lock<std::mutex> lock(mtx);
375 cv.wait(lock, [&ready]() { return ready; });
376 }
377 state->SetStopAndNotify();
378 });
379
380 {
381 std::lock_guard<std::mutex> lock(mtx);
382 ready = true;
383 }
384 cv.notify_all();
385 thread1.join();
386 thread2.join();
387
388 EXPECT_TRUE(state->GCIsWaiting() || state->GCIsRunning());
389 EXPECT_TRUE(state->StateIsStop());
390 }
391
HWTEST_F_L0(PGOProfilerTestOne,WaitDumpThenNotifyAll)392 HWTEST_F_L0(PGOProfilerTestOne, WaitDumpThenNotifyAll)
393 {
394 auto state = std::make_shared<PGOStateMock>();
395 EXPECT_TRUE(state->StateIsStop());
396 state->TryChangeState(PGOState::State::STOP, PGOState::State::START);
397 EXPECT_TRUE(state->StateIsStart());
398
399 std::mutex mtx;
400 std::condition_variable cv;
401 bool ready = false;
402
403 std::future<void> future1 = std::async(std::launch::async, [state, &mtx, &cv, &ready]() {
404 {
405 std::unique_lock<std::mutex> lock(mtx);
406 cv.wait(lock, [&ready]() { return ready; });
407 }
408 state->WaitDumpTest();
409 });
410
411 std::future<void> future2 = std::async(std::launch::async, [state, &mtx, &cv, &ready]() {
412 {
413 std::unique_lock<std::mutex> lock(mtx);
414 cv.wait(lock, [&ready]() { return ready; });
415 }
416 state->WaitDumpTest();
417 });
418
419 {
420 std::lock_guard<std::mutex> lock(mtx);
421 ready = true;
422 }
423 cv.notify_all();
424 std::this_thread::sleep_for(std::chrono::milliseconds(500));
425 state->SetStopAndNotify();
426
427 auto status1 = future1.wait_for(std::chrono::seconds(2));
428 auto status2 = future2.wait_for(std::chrono::seconds(2));
429 ASSERT_NE(status1, std::future_status::timeout) << "thread 1 may freeze";
430 ASSERT_NE(status2, std::future_status::timeout) << "thread 2 may freeze";
431 }
432
HWTEST_F_L0(PGOProfilerTestOne,SetSaveAndNotify)433 HWTEST_F_L0(PGOProfilerTestOne, SetSaveAndNotify)
434 {
435 auto state = std::make_shared<PGOStateMock>();
436 EXPECT_TRUE(state->StateIsStop());
437 state->TryChangeState(PGOState::State::STOP, PGOState::State::START);
438 EXPECT_TRUE(state->StateIsStart());
439
440 std::mutex mtx;
441 std::condition_variable cv;
442 bool ready = false;
443
444 std::future<void> future1 = std::async(std::launch::async, [state, &mtx, &cv, &ready]() {
445 {
446 std::unique_lock<std::mutex> lock(mtx);
447 cv.wait(lock, [&ready]() { return ready; });
448 }
449 state->WaitDumpTest();
450 });
451
452 std::future<void> future2 = std::async(std::launch::async, [state, &mtx, &cv, &ready]() {
453 {
454 std::unique_lock<std::mutex> lock(mtx);
455 cv.wait(lock, [&ready]() { return ready; });
456 }
457 state->WaitDumpTest();
458 });
459
460 {
461 std::lock_guard<std::mutex> lock(mtx);
462 ready = true;
463 }
464 cv.notify_all();
465 std::this_thread::sleep_for(std::chrono::milliseconds(500));
466 state->SetSaveAndNotify();
467
468 auto status1 = future1.wait_for(std::chrono::seconds(2));
469 auto status2 = future2.wait_for(std::chrono::seconds(2));
470 ASSERT_NE(status1, std::future_status::timeout) << "thread 1 may freeze";
471 ASSERT_NE(status2, std::future_status::timeout) << "thread 2 may freeze";
472
473 EXPECT_TRUE(state->StateIsSave());
474 }
475 } // namespace panda::test
476