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