• 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(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