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