blob: 0afeead6355bffffdd2a97a6f55ec3d07077f6fe [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include "base/bind.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
#include "testing/gmock/include/gmock/gmock.h"
#if defined(OS_ANDROID)
#include "base/android/build_info.h"
// TODO: Remove these definitions when upgrading to an NDK that includes them.
// They were copied from <malloc.h> in Bionic master.
extern "C" int mallopt(int __option, int __value) __attribute__((weak));
#define M_PURGE -101
#endif
using testing::Le;
using testing::Ge;
using testing::AllOf;
using memory_instrumentation::GlobalMemoryDump;
using memory_instrumentation::mojom::ProcessType;
namespace content {
class MemoryInstrumentationTest : public ContentBrowserTest {
protected:
void Navigate(Shell* shell) {
EXPECT_TRUE(NavigateToURL(shell, GetTestUrl("", "title1.html")));
}
};
uint64_t GetPrivateFootprintKb(ProcessType type,
const GlobalMemoryDump& global_dump,
base::ProcessId pid = base::kNullProcessId) {
const GlobalMemoryDump::ProcessDump* target_dump = nullptr;
for (const auto& dump : global_dump.process_dumps()) {
if (dump.process_type() != type)
continue;
if (pid != base::kNullProcessId && pid != dump.pid())
continue;
EXPECT_FALSE(target_dump);
target_dump = &dump;
}
EXPECT_TRUE(target_dump);
return target_dump->os_dump().private_footprint_kb;
}
std::unique_ptr<GlobalMemoryDump> DoGlobalDump() {
std::unique_ptr<GlobalMemoryDump> result = nullptr;
base::RunLoop run_loop;
memory_instrumentation::MemoryInstrumentation::GetInstance()
->RequestGlobalDump(
{}, base::BindOnce(
[](base::Closure quit_closure,
std::unique_ptr<GlobalMemoryDump>* out_result,
bool success, std::unique_ptr<GlobalMemoryDump> result) {
EXPECT_TRUE(success);
*out_result = std::move(result);
std::move(quit_closure).Run();
},
run_loop.QuitClosure(), &result));
run_loop.Run();
return result;
}
// *SAN fake some sys calls we need meaning we never get dumps for the
// processes.
// Flakes on Android. crbug.com/970058
#if defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER) || \
defined(THREAD_SANITIZER) || defined(OS_ANDROID)
#define MAYBE_PrivateFootprintComputation DISABLED_PrivateFootprintComputation
#else
#define MAYBE_PrivateFootprintComputation PrivateFootprintComputation
#endif
// Despite the location, this test is not tracing related.
// TODO(hjd): Move this once we have a resource_coordinator folder in browser.
IN_PROC_BROWSER_TEST_F(MemoryInstrumentationTest,
MAYBE_PrivateFootprintComputation) {
#if defined(OS_ANDROID)
// The allocator in Android N and above will defer madvising large allocations
// until the purge interval, which is set at 1 second. If we are on N or
// above, check whether we can use mallopt(M_PURGE) to trigger an immediate
// purge. If we can't, skip the test.
if (base::android::BuildInfo::GetInstance()->sdk_int() >=
base::android::SDK_VERSION_NOUGAT) {
// M_PURGE is supported on most devices running P, but not all of them. So
// we can't check the API level but must instead attempt to trigger a purge
// and check whether or not it succeeded.
if (!mallopt || mallopt(M_PURGE, 0) == 0) {
DVLOG(0) << "Skipping test - unable to trigger a purge.";
return;
}
}
#endif
Navigate(shell());
// We have to pick a big size (>=64mb) to avoid an implementation detail of
// malloc on MacOS which doesn't free or mark as reusable small allocations
// after a free.
const int64_t kAllocSize = 65 * 1024 * 1024;
const int64_t kAllocSizeKb = kAllocSize / 1024;
std::unique_ptr<GlobalMemoryDump> before_ptr = DoGlobalDump();
std::unique_ptr<char[]> buffer = std::make_unique<char[]>(kAllocSize);
memset(buffer.get(), 1, kAllocSize);
volatile char* x = static_cast<volatile char*>(buffer.get());
EXPECT_EQ(x[0] + x[kAllocSize - 1], 2);
content::WebContents* web_contents = shell()->web_contents();
base::ProcessId renderer_pid =
web_contents->GetMainFrame()->GetProcess()->GetProcess().Pid();
// Should allocate at least 4*10^6 / 1024 = 4000kb.
EXPECT_TRUE(content::ExecuteScript(web_contents,
"var a = Array(1000000).fill(1234);\n"));
std::unique_ptr<GlobalMemoryDump> during_ptr = DoGlobalDump();
buffer.reset();
#if defined(OS_ANDROID)
if (mallopt)
mallopt(M_PURGE, 0);
#endif
std::unique_ptr<GlobalMemoryDump> after_ptr = DoGlobalDump();
int64_t before_kb = GetPrivateFootprintKb(ProcessType::BROWSER, *before_ptr);
int64_t during_kb = GetPrivateFootprintKb(ProcessType::BROWSER, *during_ptr);
int64_t after_kb = GetPrivateFootprintKb(ProcessType::BROWSER, *after_ptr);
EXPECT_THAT(after_kb - before_kb,
AllOf(Ge(-kAllocSizeKb / 10), Le(kAllocSizeKb / 10)));
EXPECT_THAT(during_kb - before_kb,
AllOf(Ge(kAllocSizeKb - 3000), Le(kAllocSizeKb + 3000)));
EXPECT_THAT(during_kb - after_kb,
AllOf(Ge(kAllocSizeKb - 3000), Le(kAllocSizeKb + 3000)));
int64_t before_renderer_kb =
GetPrivateFootprintKb(ProcessType::RENDERER, *before_ptr, renderer_pid);
int64_t during_renderer_kb =
GetPrivateFootprintKb(ProcessType::RENDERER, *during_ptr, renderer_pid);
EXPECT_GE(during_renderer_kb - before_renderer_kb, 3000);
}
} // namespace content