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