• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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