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