• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-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 
16 #include <gtest/gtest.h>
17 #include <cstring>
18 #include <string>
19 
20 #include "libpandabase/utils/utils.h"
21 #include "runtime/include/runtime.h"
22 #include "runtime/include/panda_vm.h"
23 #include "runtime/include/class_linker.h"
24 #include "runtime/include/thread_scopes.h"
25 #include "runtime/mem/vm_handle.h"
26 #include "runtime/handle_scope-inl.h"
27 #include "runtime/include/coretypes/array.h"
28 #include "runtime/include/coretypes/string.h"
29 #include "runtime/mem/gc/card_table.h"
30 #include "runtime/mem/gc/g1/g1-allocator.h"
31 #include "runtime/mem/rem_set-inl.h"
32 #include "runtime/mem/region_space.h"
33 #include "runtime/tests/test_utils.h"
34 
35 #include "runtime/mem/object_helpers.h"
36 
37 namespace ark::mem {
38 class GCTestLog : public testing::TestWithParam<const char *> {
39 public:
40     NO_MOVE_SEMANTIC(GCTestLog);
41     NO_COPY_SEMANTIC(GCTestLog);
42 
43     GCTestLog() = default;
44 
~GCTestLog()45     ~GCTestLog() override
46     {
47         [[maybe_unused]] bool success = Runtime::Destroy();
48         ASSERT(success);
49         Logger::Destroy();
50     }
51 
52     // NOLINTBEGIN(readability-magic-numbers)
SetupRuntime(const std::string & gcType,bool smallHeapAndYoungSpaces=false,size_t promotionRegionAliveRate=100) const53     void SetupRuntime(const std::string &gcType, bool smallHeapAndYoungSpaces = false,
54                       size_t promotionRegionAliveRate = 100) const
55     {
56         ark::Logger::ComponentMask componentMask;
57         componentMask.set(Logger::Component::GC);
58 
59         Logger::InitializeStdLogging(Logger::Level::INFO, componentMask);
60         EXPECT_TRUE(Logger::IsLoggingOn(Logger::Level::INFO, Logger::Component::GC));
61 
62         RuntimeOptions options;
63         options.SetLoadRuntimes({"core"});
64         options.SetGcType(gcType);
65         options.SetRunGcInPlace(true);
66         options.SetCompilerEnableJit(false);
67         options.SetGcWorkersCount(0);
68         options.SetG1PromotionRegionAliveRate(promotionRegionAliveRate);
69         options.SetGcTriggerType("debug-never");
70         options.SetShouldLoadBootPandaFiles(false);
71         options.SetShouldInitializeIntrinsics(false);
72         options.SetExplicitConcurrentGcEnabled(false);
73         if (smallHeapAndYoungSpaces) {
74             options.SetYoungSpaceSize(2_MB);
75             options.SetHeapSizeLimit(15_MB);
76         }
77         [[maybe_unused]] bool success = Runtime::Create(options);
78         ASSERT(success);
79     }
80     // NOLINTEND(readability-magic-numbers)
81 
GetGCCounter(GC * gc)82     size_t GetGCCounter(GC *gc)
83     {
84         return gc->gcCounter_;
85     }
86 
CounterLogTest()87     void CounterLogTest()
88     {
89         SetupRuntime(GetParam());
90 
91         Runtime *runtime = Runtime::GetCurrent();
92         GC *gc = runtime->GetPandaVM()->GetGC();
93         GCTask task(GCTaskCause::YOUNG_GC_CAUSE);
94         const unsigned iterations = 100;
95 
96         ASSERT(GetGCCounter(gc) == 0);
97 
98         for (size_t i = 1; i < iterations; i++) {
99             testing::internal::CaptureStderr();
100             task.Run(*gc);
101             expectedLog_ = '[' + std::to_string(GetGCCounter(gc)) + ']';
102             log_ = testing::internal::GetCapturedStderr();
103             ASSERT_NE(log_.find(expectedLog_), std::string::npos) << "Expected:\n"
104                                                                   << expectedLog_ << "\nLog:\n"
105                                                                   << log_;
106             ASSERT(GetGCCounter(gc) == i);
107             task.reason = static_cast<GCTaskCause>(i % numberOfGcCauses_ == 0 ? i % numberOfGcCauses_ + 1
108                                                                               : i % numberOfGcCauses_);
109         }
110     }
111 
FullLogTest()112     void FullLogTest()
113     {
114         auto gcType = GetParam();
115         bool isStw = strcmp(gcType, "stw") == 0;
116 
117         SetupRuntime(gcType);
118 
119         Runtime *runtime = Runtime::GetCurrent();
120         GC *gc = runtime->GetPandaVM()->GetGC();
121         GCTask task(GCTaskCause::YOUNG_GC_CAUSE);
122 
123         testing::internal::CaptureStderr();
124         task.reason = GCTaskCause::YOUNG_GC_CAUSE;
125         task.Run(*gc);
126         expectedLog_ = isStw ? "[FULL (Young)]" : "[YOUNG (Young)]";
127         log_ = testing::internal::GetCapturedStderr();
128         ASSERT_NE(log_.find(expectedLog_), std::string::npos) << "Expected:\n" << expectedLog_ << "\nLog:\n" << log_;
129 
130         testing::internal::CaptureStderr();
131         task.reason = GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE;
132         task.Run(*gc);
133         expectedLog_ = isStw ? "[FULL (Threshold)]" : "[TENURED (Threshold)]";
134         log_ = testing::internal::GetCapturedStderr();
135         ASSERT_NE(log_.find(expectedLog_), std::string::npos) << "Expected:\n" << expectedLog_ << "\nLog:\n" << log_;
136 
137         testing::internal::CaptureStderr();
138         task.reason = GCTaskCause::OOM_CAUSE;
139         task.Run(*gc);
140         expectedLog_ = "[FULL (OOM)]";
141         log_ = testing::internal::GetCapturedStderr();
142         ASSERT_NE(log_.find(expectedLog_), std::string::npos) << "Expected:\n" << expectedLog_ << "\nLog:\n" << log_;
143     }
144 
145     // GCCollectionType order is important
146     static_assert(GCCollectionType::NONE < GCCollectionType::YOUNG);
147     static_assert(GCCollectionType::YOUNG < GCCollectionType::MIXED);
148     static_assert(GCCollectionType::MIXED < GCCollectionType::TENURED);
149     static_assert(GCCollectionType::TENURED < GCCollectionType::FULL);
150 
151 protected:
152     // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
153     std::string expectedLog_;
154     // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
155     std::string log_;
156 
157 private:
158     const size_t numberOfGcCauses_ = 8;
159 };
160 
TEST_P(GCTestLog,FullLogTest)161 TEST_P(GCTestLog, FullLogTest)
162 {
163     FullLogTest();
164 }
165 
TEST_F(GCTestLog,GenGCYoungCauseFullCollectionLogTest)166 TEST_F(GCTestLog, GenGCYoungCauseFullCollectionLogTest)
167 {
168     SetupRuntime("gen-gc", true);
169 
170     Runtime *runtime = Runtime::GetCurrent();
171     GC *gc = runtime->GetPandaVM()->GetGC();
172     MTManagedThread *thread = MTManagedThread::GetCurrent();
173     {
174         ScopedManagedCodeThread s(thread);
175         [[maybe_unused]] HandleScope<ObjectHeader *> scope(thread);
176         ObjectAllocator objectAllocator;
177 
178         uint32_t garbageRate = Runtime::GetOptions().GetG1RegionGarbageRateThreshold();
179         // NOLINTNEXTLINE(readability-magic-numbers)
180         size_t stringLen = garbageRate * DEFAULT_REGION_SIZE / 100U + sizeof(coretypes::String);
181 
182         // NOLINTNEXTLINE(modernize-avoid-c-arrays)
183         VMHandle<coretypes::Array> arrays[3U];
184         {
185             arrays[0] =
186                 VMHandle<coretypes::Array>(thread, objectAllocator.AllocArray(2U, ClassRoot::ARRAY_STRING, false));
187             arrays[0]->Set(0, objectAllocator.AllocString(stringLen));
188         }
189     }
190 
191     GCTask task(GCTaskCause::YOUNG_GC_CAUSE);
192     testing::internal::CaptureStderr();
193     task.Run(*gc);
194     expectedLog_ = "[FULL (Young)]";
195     log_ = testing::internal::GetCapturedStderr();
196     ASSERT_NE(log_.find(expectedLog_), std::string::npos) << "Expected:\n" << expectedLog_ << "\nLog:\n" << log_;
197 }
198 
TEST_F(GCTestLog,G1GCMixedCollectionLogTest)199 TEST_F(GCTestLog, G1GCMixedCollectionLogTest)
200 {
201     SetupRuntime("g1-gc");
202 
203     uint32_t garbageRate = Runtime::GetOptions().GetG1RegionGarbageRateThreshold();
204     // NOLINTNEXTLINE(readability-magic-numbers)
205     size_t bigStringLen = garbageRate * DEFAULT_REGION_SIZE / 100U + sizeof(coretypes::String);
206     // NOLINTNEXTLINE(readability-magic-numbers)
207     size_t bigStringLen1 = (garbageRate + 1) * DEFAULT_REGION_SIZE / 100U + sizeof(coretypes::String);
208     // NOLINTNEXTLINE(readability-magic-numbers)
209     size_t bigStringLen2 = (garbageRate + 2U) * DEFAULT_REGION_SIZE / 100U + sizeof(coretypes::String);
210     size_t smallLen = DEFAULT_REGION_SIZE / 2U + sizeof(coretypes::String);
211 
212     GC *gc = Runtime::GetCurrent()->GetPandaVM()->GetGC();
213     MTManagedThread *thread = MTManagedThread::GetCurrent();
214     ScopedManagedCodeThread s(thread);
215     [[maybe_unused]] HandleScope<ObjectHeader *> scope(thread);
216 
217     ObjectAllocator objectAllocator;
218 
219     testing::internal::CaptureStderr();
220 
221     VMHandle<coretypes::Array> holder;
222     VMHandle<ObjectHeader> young;
223     holder = VMHandle<coretypes::Array>(thread, objectAllocator.AllocArray(4U, ClassRoot::ARRAY_STRING, false));
224     holder->Set(0_I, objectAllocator.AllocString(bigStringLen));
225     holder->Set(1_I, objectAllocator.AllocString(bigStringLen1));
226     holder->Set(2_I, objectAllocator.AllocString(bigStringLen2));
227     holder->Set(3_I, objectAllocator.AllocString(smallLen));
228 
229     {
230         ScopedNativeCodeThread sn(thread);
231         GCTask task(GCTaskCause::YOUNG_GC_CAUSE);
232         task.Run(*gc);
233     }
234     expectedLog_ = "[YOUNG (Young)]";
235     log_ = testing::internal::GetCapturedStderr();
236     ASSERT_NE(log_.find(expectedLog_), std::string::npos) << "Expected:\n" << expectedLog_ << "\nLog:\n" << log_;
237     testing::internal::CaptureStderr();
238 
239     VMHandle<ObjectHeader> current;
240     current = VMHandle<ObjectHeader>(thread, objectAllocator.AllocArray(smallLen, ClassRoot::ARRAY_U8, false));
241 
242     holder->Set(0_I, static_cast<ObjectHeader *>(nullptr));
243     holder->Set(1_I, static_cast<ObjectHeader *>(nullptr));
244     holder->Set(2_I, static_cast<ObjectHeader *>(nullptr));
245     holder->Set(3_I, static_cast<ObjectHeader *>(nullptr));
246 
247     {
248         ScopedNativeCodeThread sn(thread);
249         GCTask task1(GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE);
250         task1.Run(*gc);
251     }
252 
253     young = VMHandle<ObjectHeader>(thread, objectAllocator.AllocObjectInYoung());
254     {
255         ScopedNativeCodeThread sn(thread);
256         GCTask task2(GCTaskCause::YOUNG_GC_CAUSE);
257         task2.Run(*gc);
258     }
259     expectedLog_ = "[MIXED (Young)]";
260     log_ = testing::internal::GetCapturedStderr();
261     ASSERT_NE(log_.find(expectedLog_), std::string::npos) << "Expected:\n" << expectedLog_ << "\nLog:\n" << log_;
262 }
263 
TEST_P(GCTestLog,CounterLogTest)264 TEST_P(GCTestLog, CounterLogTest)
265 {
266     CounterLogTest();
267 }
268 
269 INSTANTIATE_TEST_SUITE_P(GCTestLogOnDiffGCs, GCTestLog, ::testing::ValuesIn(TESTED_GC));
270 }  // namespace ark::mem
271