1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <stdint.h>
6
7 #include "base/bind.h"
8 #include "base/run_loop.h"
9 #include "build/build_config.h"
10 #include "content/public/browser/render_frame_host.h"
11 #include "content/public/browser/render_process_host.h"
12 #include "content/public/browser/web_contents.h"
13 #include "content/public/test/browser_test_utils.h"
14 #include "content/public/test/content_browser_test.h"
15 #include "content/public/test/content_browser_test_utils.h"
16 #include "content/shell/browser/shell.h"
17 #include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
18 #include "testing/gmock/include/gmock/gmock.h"
19
20 #if defined(OS_ANDROID)
21 #include "base/android/build_info.h"
22
23 // TODO: Remove these definitions when upgrading to an NDK that includes them.
24 // They were copied from <malloc.h> in Bionic master.
25 extern "C" int mallopt(int __option, int __value) __attribute__((weak));
26 #define M_PURGE -101
27 #endif
28
29 using testing::Le;
30 using testing::Ge;
31 using testing::AllOf;
32 using memory_instrumentation::GlobalMemoryDump;
33 using memory_instrumentation::mojom::ProcessType;
34
35 namespace content {
36
37 class MemoryInstrumentationTest : public ContentBrowserTest {
38 protected:
Navigate(Shell * shell)39 void Navigate(Shell* shell) {
40 EXPECT_TRUE(NavigateToURL(shell, GetTestUrl("", "title1.html")));
41 }
42 };
43
GetPrivateFootprintKb(ProcessType type,const GlobalMemoryDump & global_dump,base::ProcessId pid=base::kNullProcessId)44 uint64_t GetPrivateFootprintKb(ProcessType type,
45 const GlobalMemoryDump& global_dump,
46 base::ProcessId pid = base::kNullProcessId) {
47 const GlobalMemoryDump::ProcessDump* target_dump = nullptr;
48 for (const auto& dump : global_dump.process_dumps()) {
49 if (dump.process_type() != type)
50 continue;
51
52 if (pid != base::kNullProcessId && pid != dump.pid())
53 continue;
54
55 EXPECT_FALSE(target_dump);
56 target_dump = &dump;
57 }
58 EXPECT_TRUE(target_dump);
59 return target_dump->os_dump().private_footprint_kb;
60 }
61
DoGlobalDump()62 std::unique_ptr<GlobalMemoryDump> DoGlobalDump() {
63 std::unique_ptr<GlobalMemoryDump> result = nullptr;
64 base::RunLoop run_loop;
65 memory_instrumentation::MemoryInstrumentation::GetInstance()
66 ->RequestGlobalDump(
67 {}, base::BindOnce(
68 [](base::OnceClosure quit_closure,
69 std::unique_ptr<GlobalMemoryDump>* out_result,
70 bool success, std::unique_ptr<GlobalMemoryDump> result) {
71 EXPECT_TRUE(success);
72 *out_result = std::move(result);
73 std::move(quit_closure).Run();
74 },
75 run_loop.QuitClosure(), &result));
76 run_loop.Run();
77 return result;
78 }
79
80 // *SAN fake some sys calls we need meaning we never get dumps for the
81 // processes.
82 #if defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER) || \
83 defined(THREAD_SANITIZER)
84 #define MAYBE_PrivateFootprintComputation DISABLED_PrivateFootprintComputation
85 #else
86 #define MAYBE_PrivateFootprintComputation PrivateFootprintComputation
87 #endif
88
89 // Despite the location, this test is not tracing related.
90 // TODO(hjd): Move this once we have a resource_coordinator folder in browser.
IN_PROC_BROWSER_TEST_F(MemoryInstrumentationTest,MAYBE_PrivateFootprintComputation)91 IN_PROC_BROWSER_TEST_F(MemoryInstrumentationTest,
92 MAYBE_PrivateFootprintComputation) {
93 #if defined(OS_ANDROID)
94 // The allocator in Android N and above will defer madvising large allocations
95 // until the purge interval, which is set at 1 second. If we are on N or
96 // above, check whether we can use mallopt(M_PURGE) to trigger an immediate
97 // purge. If we can't, skip the test.
98 if (base::android::BuildInfo::GetInstance()->sdk_int() >=
99 base::android::SDK_VERSION_NOUGAT) {
100 // M_PURGE is supported on most devices running P, but not all of them. So
101 // we can't check the API level but must instead attempt to trigger a purge
102 // and check whether or not it succeeded.
103 if (!mallopt || mallopt(M_PURGE, 0) == 0) {
104 DVLOG(0) << "Skipping test - unable to trigger a purge.";
105 return;
106 }
107 }
108 #endif
109
110 Navigate(shell());
111
112 // We have to pick a big size (>=64mb) to avoid an implementation detail of
113 // malloc on MacOS which doesn't free or mark as reusable small allocations
114 // after a free.
115 const int64_t kAllocSize = 65 * 1024 * 1024;
116 const int64_t kAllocSizeKb = kAllocSize / 1024;
117
118 std::unique_ptr<GlobalMemoryDump> before_ptr = DoGlobalDump();
119
120 std::unique_ptr<char[]> buffer = std::make_unique<char[]>(kAllocSize);
121 memset(buffer.get(), 1, kAllocSize);
122 volatile char* x = static_cast<volatile char*>(buffer.get());
123 EXPECT_EQ(x[0] + x[kAllocSize - 1], 2);
124
125 content::WebContents* web_contents = shell()->web_contents();
126 base::ProcessId renderer_pid =
127 web_contents->GetMainFrame()->GetProcess()->GetProcess().Pid();
128
129 // Should allocate at least 4*10^6 / 1024 = 4000kb.
130 EXPECT_TRUE(content::ExecuteScript(web_contents,
131 "var a = Array(1000000).fill(1234);\n"));
132
133 std::unique_ptr<GlobalMemoryDump> during_ptr = DoGlobalDump();
134
135 buffer.reset();
136
137 #if defined(OS_ANDROID)
138 if (mallopt)
139 mallopt(M_PURGE, 0);
140 #endif
141
142 std::unique_ptr<GlobalMemoryDump> after_ptr = DoGlobalDump();
143
144 int64_t before_kb = GetPrivateFootprintKb(ProcessType::BROWSER, *before_ptr);
145 int64_t during_kb = GetPrivateFootprintKb(ProcessType::BROWSER, *during_ptr);
146 int64_t after_kb = GetPrivateFootprintKb(ProcessType::BROWSER, *after_ptr);
147
148 EXPECT_THAT(after_kb - before_kb,
149 AllOf(Ge(-kAllocSizeKb / 10), Le(kAllocSizeKb / 10)));
150 EXPECT_THAT(during_kb - before_kb,
151 AllOf(Ge(kAllocSizeKb - 3000), Le(kAllocSizeKb + 3000)));
152 EXPECT_THAT(during_kb - after_kb,
153 AllOf(Ge(kAllocSizeKb - 3000), Le(kAllocSizeKb + 3000)));
154
155 int64_t before_renderer_kb =
156 GetPrivateFootprintKb(ProcessType::RENDERER, *before_ptr, renderer_pid);
157 int64_t during_renderer_kb =
158 GetPrivateFootprintKb(ProcessType::RENDERER, *during_ptr, renderer_pid);
159 EXPECT_GE(during_renderer_kb - before_renderer_kb, 3000);
160 }
161
162 } // namespace content
163