1 /*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "third_party/blink/renderer/core/timing/memory_info.h"
32
33 #include "base/test/test_mock_time_task_runner.h"
34 #include "base/time/default_tick_clock.h"
35 #include "testing/gtest/include/gtest/gtest.h"
36 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
37 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
38
39 namespace blink {
40
TEST(MemoryInfo,quantizeMemorySize)41 TEST(MemoryInfo, quantizeMemorySize) {
42 EXPECT_EQ(10000000u, QuantizeMemorySize(1024));
43 EXPECT_EQ(10000000u, QuantizeMemorySize(1024 * 1024));
44 EXPECT_EQ(410000000u, QuantizeMemorySize(389472983));
45 EXPECT_EQ(39600000u, QuantizeMemorySize(38947298));
46 EXPECT_EQ(29400000u, QuantizeMemorySize(28947298));
47 EXPECT_EQ(19300000u, QuantizeMemorySize(18947298));
48 EXPECT_EQ(14300000u, QuantizeMemorySize(13947298));
49 EXPECT_EQ(10000000u, QuantizeMemorySize(3894729));
50 EXPECT_EQ(10000000u, QuantizeMemorySize(389472));
51 EXPECT_EQ(10000000u, QuantizeMemorySize(38947));
52 EXPECT_EQ(10000000u, QuantizeMemorySize(3894));
53 EXPECT_EQ(10000000u, QuantizeMemorySize(389));
54 EXPECT_EQ(10000000u, QuantizeMemorySize(38));
55 EXPECT_EQ(10000000u, QuantizeMemorySize(3));
56 EXPECT_EQ(10000000u, QuantizeMemorySize(1));
57 EXPECT_EQ(10000000u, QuantizeMemorySize(0));
58 // Rounding differences between OS's may affect the precise value of the last
59 // bucket.
60 EXPECT_LE(3760000000u,
61 QuantizeMemorySize(std::numeric_limits<size_t>::max()));
62 EXPECT_GT(4000000000u,
63 QuantizeMemorySize(std::numeric_limits<size_t>::max()));
64 }
65
66 static constexpr int kModForBucketizationCheck = 100000;
67
68 class MemoryInfoTest : public testing::Test {
69 protected:
CheckValues(MemoryInfo * info,MemoryInfo::Precision precision)70 void CheckValues(MemoryInfo* info, MemoryInfo::Precision precision) {
71 // Check that used <= total <= limit.
72
73 // TODO(npm): add a check usedJSHeapSize <= totalJSHeapSize once it always
74 // holds. See https://crbug.com/849322
75 EXPECT_LE(info->totalJSHeapSize(), info->jsHeapSizeLimit());
76 if (precision == MemoryInfo::Precision::Bucketized) {
77 // Check that the bucketized values are heavily rounded.
78 EXPECT_EQ(0u, info->totalJSHeapSize() % kModForBucketizationCheck);
79 EXPECT_EQ(0u, info->usedJSHeapSize() % kModForBucketizationCheck);
80 EXPECT_EQ(0u, info->jsHeapSizeLimit() % kModForBucketizationCheck);
81 } else {
82 // Check that the precise values are not heavily rounded.
83 // Note: these checks are potentially flaky but in practice probably never
84 // flaky. If this is noticed to be flaky, disable test and assign bug to
85 // npm@.
86 EXPECT_NE(0u, info->totalJSHeapSize() % kModForBucketizationCheck);
87 EXPECT_NE(0u, info->usedJSHeapSize() % kModForBucketizationCheck);
88 EXPECT_NE(0u, info->jsHeapSizeLimit() % kModForBucketizationCheck);
89 }
90 }
91
CheckEqual(MemoryInfo * info,MemoryInfo * info2)92 void CheckEqual(MemoryInfo* info, MemoryInfo* info2) {
93 EXPECT_EQ(info2->totalJSHeapSize(), info->totalJSHeapSize());
94 EXPECT_EQ(info2->usedJSHeapSize(), info->usedJSHeapSize());
95 EXPECT_EQ(info2->jsHeapSizeLimit(), info->jsHeapSizeLimit());
96 }
97 };
98
99 struct MemoryInfoTestScopedMockTime {
MemoryInfoTestScopedMockTimeblink::MemoryInfoTestScopedMockTime100 MemoryInfoTestScopedMockTime(MemoryInfo::Precision precision) {
101 MemoryInfo::SetTickClockForTestingForCurrentThread(
102 test_task_runner_->GetMockTickClock());
103 }
104
~MemoryInfoTestScopedMockTimeblink::MemoryInfoTestScopedMockTime105 ~MemoryInfoTestScopedMockTime() {
106 // MemoryInfo creates a HeapSizeCache object which lives in the current
107 // thread. This means that it will be shared by all the tests when
108 // executed sequentially. We must ensure that it ends up in a consistent
109 // state after each test execution.
110 MemoryInfo::SetTickClockForTestingForCurrentThread(
111 base::DefaultTickClock::GetInstance());
112 }
113
AdvanceClockblink::MemoryInfoTestScopedMockTime114 void AdvanceClock(base::TimeDelta delta) {
115 test_task_runner_->FastForwardBy(delta);
116 }
117
118 scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_ =
119 base::MakeRefCounted<base::TestMockTimeTaskRunner>();
120 };
121
TEST_F(MemoryInfoTest,Bucketized)122 TEST_F(MemoryInfoTest, Bucketized) {
123 V8TestingScope scope;
124 v8::Isolate* isolate = scope.GetIsolate();
125 // The vector is used to keep the objects
126 // allocated alive even if GC happens. In practice, the objects only get GC'd
127 // after we go out of V8TestingScope. But having them in a vector makes it
128 // impossible for GC to clear them up unexpectedly early.
129 Vector<v8::Local<v8::ArrayBuffer>> objects;
130
131 MemoryInfoTestScopedMockTime mock_time(MemoryInfo::Precision::Bucketized);
132 MemoryInfo* bucketized_memory =
133 MakeGarbageCollected<MemoryInfo>(MemoryInfo::Precision::Bucketized);
134
135 // Check that the values are monotone and rounded.
136 CheckValues(bucketized_memory, MemoryInfo::Precision::Bucketized);
137
138 // Advance the clock for a minute. Not enough to make bucketized value
139 // recalculate. Also allocate some memory.
140 mock_time.AdvanceClock(base::TimeDelta::FromMinutes(1));
141 objects.push_back(v8::ArrayBuffer::New(isolate, 100));
142
143 MemoryInfo* bucketized_memory2 =
144 MakeGarbageCollected<MemoryInfo>(MemoryInfo::Precision::Bucketized);
145 // The old bucketized values must be equal to the new bucketized values.
146 CheckEqual(bucketized_memory, bucketized_memory2);
147
148 // TODO(npm): The bucketized MemoryInfo is very hard to change reliably. One
149 // option is to do something such as:
150 // for (int i = 0; i < kNumArrayBuffersForLargeAlloc; i++)
151 // objects.push_back(v8::ArrayBuffer::New(isolate, 1));
152 // Here, kNumArrayBuffersForLargeAlloc should be strictly greater than 200000
153 // (test failed on Windows with this value). Creating a single giant
154 // ArrayBuffer does not seem to work, so instead a lot of small ArrayBuffers
155 // are used. For now we only test that values are still rounded after adding
156 // some memory.
157 for (int i = 0; i < 10; i++) {
158 // Advance the clock for another thirty minutes, enough to make the
159 // bucketized value recalculate.
160 mock_time.AdvanceClock(base::TimeDelta::FromMinutes(30));
161 objects.push_back(v8::ArrayBuffer::New(isolate, 100));
162 MemoryInfo* bucketized_memory3 =
163 MakeGarbageCollected<MemoryInfo>(MemoryInfo::Precision::Bucketized);
164 CheckValues(bucketized_memory3, MemoryInfo::Precision::Bucketized);
165 // The limit should remain unchanged.
166 EXPECT_EQ(bucketized_memory3->jsHeapSizeLimit(),
167 bucketized_memory->jsHeapSizeLimit());
168 }
169 }
170
TEST_F(MemoryInfoTest,Precise)171 TEST_F(MemoryInfoTest, Precise) {
172 V8TestingScope scope;
173 v8::Isolate* isolate = scope.GetIsolate();
174 Vector<v8::Local<v8::ArrayBuffer>> objects;
175
176 MemoryInfoTestScopedMockTime mock_time(MemoryInfo::Precision::Precise);
177 MemoryInfo* precise_memory =
178 MakeGarbageCollected<MemoryInfo>(MemoryInfo::Precision::Precise);
179 // Check that the precise values are monotone and not heavily rounded.
180 CheckValues(precise_memory, MemoryInfo::Precision::Precise);
181
182 // Advance the clock for a nanosecond, which should not be enough to make the
183 // precise value recalculate.
184 mock_time.AdvanceClock(base::TimeDelta::FromNanoseconds(1));
185 // Allocate an object in heap and keep it in a vector to make sure that it
186 // does not get accidentally GC'd. This single ArrayBuffer should be enough to
187 // be noticed by the used heap size in the precise MemoryInfo case.
188 objects.push_back(v8::ArrayBuffer::New(isolate, 100));
189 MemoryInfo* precise_memory2 =
190 MakeGarbageCollected<MemoryInfo>(MemoryInfo::Precision::Precise);
191 // The old precise values must be equal to the new precise values.
192 CheckEqual(precise_memory, precise_memory2);
193
194 for (int i = 0; i < 10; i++) {
195 // Advance the clock for another thirty seconds, enough to make the precise
196 // values be recalculated. Also allocate another object.
197 mock_time.AdvanceClock(base::TimeDelta::FromSeconds(30));
198 objects.push_back(v8::ArrayBuffer::New(isolate, 100));
199
200 MemoryInfo* new_precise_memory =
201 MakeGarbageCollected<MemoryInfo>(MemoryInfo::Precision::Precise);
202
203 CheckValues(new_precise_memory, MemoryInfo::Precision::Precise);
204 // The old precise used heap size must be different from the new one.
205 EXPECT_NE(new_precise_memory->usedJSHeapSize(),
206 precise_memory->usedJSHeapSize());
207 // The limit should remain unchanged.
208 EXPECT_EQ(new_precise_memory->jsHeapSizeLimit(),
209 precise_memory->jsHeapSizeLimit());
210 // Update |precise_memory| to be the newest MemoryInfo thus far.
211 precise_memory = new_precise_memory;
212 }
213 }
214
TEST_F(MemoryInfoTest,FlagEnabled)215 TEST_F(MemoryInfoTest, FlagEnabled) {
216 ScopedPreciseMemoryInfoForTest precise_memory_info(true);
217 V8TestingScope scope;
218 v8::Isolate* isolate = scope.GetIsolate();
219 Vector<v8::Local<v8::ArrayBuffer>> objects;
220
221 // Using MemoryInfo::Precision::Bucketized to ensure that the runtime-enabled
222 // flag overrides the Precision passed onto the method.
223 MemoryInfo* precise_memory =
224 MakeGarbageCollected<MemoryInfo>(MemoryInfo::Precision::Bucketized);
225 // Check that the precise values are monotone and not heavily rounded.
226 CheckValues(precise_memory, MemoryInfo::Precision::Precise);
227
228 // Allocate an object in heap and keep it in a vector to make sure that it
229 // does not get accidentally GC'd. This single ArrayBuffer should be enough to
230 // be noticed by the used heap size immediately since the
231 // PreciseMemoryInfoEnabled flag is on.
232 objects.push_back(v8::ArrayBuffer::New(isolate, 100));
233 MemoryInfo* precise_memory2 =
234 MakeGarbageCollected<MemoryInfo>(MemoryInfo::Precision::Bucketized);
235 CheckValues(precise_memory2, MemoryInfo::Precision::Precise);
236 // The old precise JS heap size value must NOT be equal to the new value.
237 EXPECT_NE(precise_memory2->usedJSHeapSize(),
238 precise_memory->usedJSHeapSize());
239 }
240
TEST_F(MemoryInfoTest,ZeroTime)241 TEST_F(MemoryInfoTest, ZeroTime) {
242 // In this test, we make sure that even if the current base::TimeTicks() value
243 // is very close to 0, we still obtain memory information from the first call
244 // to MemoryInfo::Create.
245 MemoryInfoTestScopedMockTime mock_time(MemoryInfo::Precision::Precise);
246 mock_time.AdvanceClock(base::TimeDelta::FromMicroseconds(100));
247 V8TestingScope scope;
248 v8::Isolate* isolate = scope.GetIsolate();
249 Vector<v8::Local<v8::ArrayBuffer>> objects;
250 objects.push_back(v8::ArrayBuffer::New(isolate, 100));
251
252 MemoryInfo* precise_memory =
253 MakeGarbageCollected<MemoryInfo>(MemoryInfo::Precision::Precise);
254 CheckValues(precise_memory, MemoryInfo::Precision::Precise);
255 EXPECT_LT(0u, precise_memory->usedJSHeapSize());
256 EXPECT_LT(0u, precise_memory->totalJSHeapSize());
257 EXPECT_LT(0u, precise_memory->jsHeapSizeLimit());
258 }
259
260 } // namespace blink
261