Commit e341d03f authored by Vlad Tsyrklevich's avatar Vlad Tsyrklevich Committed by Commit Bot

GWP-ASan: Report allocator OOM metric

Long-lived allocations can consume all of GWP-ASan's memory early,
resulting in uneven sampling over the process' lifetime. Report how many
total allocations (e.g. taking into account the the sampling frequency)
it took before OOM. Break these reports out by allocator and process
since behavior varies significantly based on these factors.

This metric will help measure whether we are frequently running out of
allocations early for a given allocator/process and will help track
future memory improvements.

Bug: 963711
Change-Id: Ifef2d61ee3fb589a4b55fcf2b93f7f64096e43f1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1628288
Auto-Submit: Vlad Tsyrklevich <vtsyrklevich@chromium.org>
Reviewed-by: default avatarBrian White <bcwhite@chromium.org>
Reviewed-by: default avatarVitaly Buka <vitalybuka@chromium.org>
Reviewed-by: default avatarKen Rockot <rockot@google.com>
Commit-Queue: Vlad Tsyrklevich <vtsyrklevich@chromium.org>
Cr-Commit-Position: refs/heads/master@{#663953}
parent 66aef181
...@@ -574,7 +574,8 @@ void ChromeMainDelegate::PostFieldTrialInitialization() { ...@@ -574,7 +574,8 @@ void ChromeMainDelegate::PostFieldTrialInitialization() {
std::string process_type = std::string process_type =
command_line.GetSwitchValueASCII(switches::kProcessType); command_line.GetSwitchValueASCII(switches::kProcessType);
bool is_browser_process = process_type.empty(); bool is_browser_process = process_type.empty();
gwp_asan::EnableForMalloc(is_canary_dev || is_browser_process); gwp_asan::EnableForMalloc(is_canary_dev || is_browser_process,
process_type.c_str());
} }
#endif #endif
...@@ -583,7 +584,11 @@ void ChromeMainDelegate::PostFieldTrialInitialization() { ...@@ -583,7 +584,11 @@ void ChromeMainDelegate::PostFieldTrialInitialization() {
version_info::Channel channel = chrome::GetChannel(); version_info::Channel channel = chrome::GetChannel();
bool is_canary_dev = (channel == version_info::Channel::CANARY || bool is_canary_dev = (channel == version_info::Channel::CANARY ||
channel == version_info::Channel::DEV); channel == version_info::Channel::DEV);
gwp_asan::EnableForPartitionAlloc(is_canary_dev); const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
std::string process_type =
command_line.GetSwitchValueASCII(switches::kProcessType);
gwp_asan::EnableForPartitionAlloc(is_canary_dev, process_type.c_str());
} }
#endif #endif
} }
......
...@@ -13,6 +13,8 @@ component("client") { ...@@ -13,6 +13,8 @@ component("client") {
"guarded_page_allocator_win.cc", "guarded_page_allocator_win.cc",
"gwp_asan.cc", "gwp_asan.cc",
"gwp_asan.h", "gwp_asan.h",
"sampling_helpers.cc",
"sampling_helpers.h",
"sampling_state.h", "sampling_state.h",
] ]
...@@ -49,6 +51,7 @@ source_set("unit_tests") { ...@@ -49,6 +51,7 @@ source_set("unit_tests") {
sources = [ sources = [
"guarded_page_allocator_unittest.cc", "guarded_page_allocator_unittest.cc",
"gwp_asan_unittest.cc", "gwp_asan_unittest.cc",
"sampling_helpers_unittest.cc",
] ]
if (use_allocator_shim) { if (use_allocator_shim) {
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "components/gwp_asan/client/guarded_page_allocator.h" #include "components/gwp_asan/client/guarded_page_allocator.h"
#include "components/gwp_asan/client/sampling_helpers.h"
#if BUILDFLAG(USE_ALLOCATOR_SHIM) #if BUILDFLAG(USE_ALLOCATOR_SHIM)
#include "components/gwp_asan/client/sampling_malloc_shims.h" #include "components/gwp_asan/client/sampling_malloc_shims.h"
...@@ -178,7 +179,7 @@ GWP_ASAN_EXPORT base::Optional<AllocatorSettings> GetAllocatorSettings( ...@@ -178,7 +179,7 @@ GWP_ASAN_EXPORT base::Optional<AllocatorSettings> GetAllocatorSettings(
} // namespace internal } // namespace internal
void EnableForMalloc(bool boost_sampling) { void EnableForMalloc(bool boost_sampling, const char* process_type) {
#if BUILDFLAG(USE_ALLOCATOR_SHIM) #if BUILDFLAG(USE_ALLOCATOR_SHIM)
static bool init_once = [&]() -> bool { static bool init_once = [&]() -> bool {
auto settings = internal::GetAllocatorSettings(internal::kGwpAsanMalloc, auto settings = internal::GetAllocatorSettings(internal::kGwpAsanMalloc,
...@@ -186,9 +187,11 @@ void EnableForMalloc(bool boost_sampling) { ...@@ -186,9 +187,11 @@ void EnableForMalloc(bool boost_sampling) {
if (!settings) if (!settings)
return false; return false;
internal::InstallMallocHooks(settings->max_allocated_pages, internal::InstallMallocHooks(
settings->num_metadata, settings->total_pages, settings->max_allocated_pages, settings->num_metadata,
settings->sampling_frequency); settings->total_pages, settings->sampling_frequency,
internal::CreateOomCallback("Malloc", process_type,
settings->sampling_frequency));
return true; return true;
}(); }();
ignore_result(init_once); ignore_result(init_once);
...@@ -198,7 +201,7 @@ void EnableForMalloc(bool boost_sampling) { ...@@ -198,7 +201,7 @@ void EnableForMalloc(bool boost_sampling) {
#endif // BUILDFLAG(USE_ALLOCATOR_SHIM) #endif // BUILDFLAG(USE_ALLOCATOR_SHIM)
} }
void EnableForPartitionAlloc(bool boost_sampling) { void EnableForPartitionAlloc(bool boost_sampling, const char* process_type) {
#if BUILDFLAG(USE_PARTITION_ALLOC) #if BUILDFLAG(USE_PARTITION_ALLOC)
static bool init_once = [&]() -> bool { static bool init_once = [&]() -> bool {
auto settings = internal::GetAllocatorSettings( auto settings = internal::GetAllocatorSettings(
...@@ -208,7 +211,9 @@ void EnableForPartitionAlloc(bool boost_sampling) { ...@@ -208,7 +211,9 @@ void EnableForPartitionAlloc(bool boost_sampling) {
internal::InstallPartitionAllocHooks( internal::InstallPartitionAllocHooks(
settings->max_allocated_pages, settings->num_metadata, settings->max_allocated_pages, settings->num_metadata,
settings->total_pages, settings->sampling_frequency); settings->total_pages, settings->sampling_frequency,
internal::CreateOomCallback("PartitionAlloc", process_type,
settings->sampling_frequency));
return true; return true;
}(); }();
ignore_result(init_once); ignore_result(init_once);
......
...@@ -24,10 +24,14 @@ struct AllocatorSettings { ...@@ -24,10 +24,14 @@ struct AllocatorSettings {
// Enable GWP-ASan for the current process for the given allocator. This should // Enable GWP-ASan for the current process for the given allocator. This should
// only be called once per process. This can not be disabled once it has been // only be called once per process. This can not be disabled once it has been
// enabled. The |boost_sampling| parameter is used to indicate if the caller // enabled. The |boost_sampling| parameter is used to indicate if the caller
// wants to increase the sampling for this process. // wants to increase the sampling for this process. The |process_type| parameter
// should be equal to the string passed to --type=, it is used for reporting
GWP_ASAN_EXPORT void EnableForMalloc(bool boost_sampling); // metrics broken out per-process.
GWP_ASAN_EXPORT void EnableForPartitionAlloc(bool boost_sampling);
GWP_ASAN_EXPORT void EnableForMalloc(bool boost_sampling,
const char* process_type);
GWP_ASAN_EXPORT void EnableForPartitionAlloc(bool boost_sampling,
const char* process_type);
} // namespace gwp_asan } // namespace gwp_asan
......
// Copyright 2019 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 "components/gwp_asan/client/sampling_helpers.h"
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/safe_math.h"
#include "base/strings/stringprintf.h"
namespace gwp_asan {
namespace internal {
namespace {
// Turns the value passed to --type= into a string used in a histogram name for
// common processes, or nullptr otherwise.
const char* ProcessString(const char* process_type) {
if (!process_type)
return nullptr;
if (!strcmp(process_type, ""))
return "Browser";
if (!strcmp(process_type, "extension"))
return "Extension";
if (!strcmp(process_type, "gpu-process"))
return "Gpu";
if (!strcmp(process_type, "ppapi"))
return "Ppapi";
if (!strcmp(process_type, "renderer"))
return "Renderer";
if (!strcmp(process_type, "utility"))
return "Utility";
return nullptr;
}
void ReportOomHistogram(std::string histogram_name,
size_t sampling_frequency,
size_t allocations) {
base::CheckedNumeric<int> total_allocations = allocations;
total_allocations *= sampling_frequency;
if (total_allocations.IsValid()) {
base::UmaHistogramCustomCounts(/*name=*/histogram_name,
/*sample=*/total_allocations.ValueOrDie(),
/*min=*/1, /*max=*/100000000,
/*buckets=*/100);
}
}
} // namespace
GuardedPageAllocator::OutOfMemoryCallback CreateOomCallback(
const char* allocator_name,
const char* process_type,
size_t sampling_frequency) {
const char* process_str = ProcessString(process_type);
if (!process_str)
return base::DoNothing();
std::string histogram_name = base::StringPrintf("GwpAsan.AllocatorOom.%s.%s",
allocator_name, process_str);
return base::BindOnce(&ReportOomHistogram, std::move(histogram_name),
sampling_frequency);
}
} // namespace internal
} // namespace gwp_asan
// Copyright (c) 2019 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.
#ifndef COMPONENTS_GWP_ASAN_CLIENT_SAMPLING_HELPERS_H_
#define COMPONENTS_GWP_ASAN_CLIENT_SAMPLING_HELPERS_H_
#include "components/gwp_asan/client/export.h"
#include "components/gwp_asan/client/guarded_page_allocator.h"
namespace gwp_asan {
namespace internal {
// Create a callback for GuardedPageAllocator that reports allocator OOM.
GWP_ASAN_EXPORT GuardedPageAllocator::OutOfMemoryCallback CreateOomCallback(
const char* allocator_name,
const char* process_type,
size_t sampling_frequency);
} // namespace internal
} // namespace gwp_asan
#endif // COMPONENTS_GWP_ASAN_CLIENT_SAMPLING_HELPERS_H_
// Copyright 2019 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 "components/gwp_asan/client/sampling_helpers.h"
#include "base/test/gtest_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gwp_asan {
namespace internal {
TEST(SamplingHelpers, BasicFunctionality) {
constexpr size_t kSamplingFrequency = 10;
constexpr size_t kAllocationsBeforeOom = 15;
base::HistogramTester histogram_tester;
CreateOomCallback("Foo", "", kSamplingFrequency).Run(kAllocationsBeforeOom);
CreateOomCallback("Bar", "utility", kSamplingFrequency)
.Run(kAllocationsBeforeOom);
// Ignore unknown process types
CreateOomCallback("Baz", "UNKNOWN", kSamplingFrequency)
.Run(kAllocationsBeforeOom);
base::HistogramTester::CountsMap expected_counts;
expected_counts["GwpAsan.AllocatorOom.Foo.Browser"] = 1;
expected_counts["GwpAsan.AllocatorOom.Bar.Utility"] = 1;
EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix("GwpAsan.AllocatorOom"),
testing::ContainerEq(expected_counts));
histogram_tester.ExpectUniqueSample(
"GwpAsan.AllocatorOom.Foo.Browser",
kSamplingFrequency * kAllocationsBeforeOom, 1);
histogram_tester.ExpectUniqueSample(
"GwpAsan.AllocatorOom.Bar.Utility",
kSamplingFrequency * kAllocationsBeforeOom, 1);
}
} // namespace internal
} // namespace gwp_asan
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
#include "components/gwp_asan/client/sampling_malloc_shims.h" #include "components/gwp_asan/client/sampling_malloc_shims.h"
#include <algorithm> #include <algorithm>
#include <utility>
#include "base/allocator/allocator_shim.h" #include "base/allocator/allocator_shim.h"
#include "base/bind_helpers.h"
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/no_destructor.h" #include "base/no_destructor.h"
...@@ -252,10 +252,11 @@ GWP_ASAN_EXPORT GuardedPageAllocator& GetMallocGpaForTesting() { ...@@ -252,10 +252,11 @@ GWP_ASAN_EXPORT GuardedPageAllocator& GetMallocGpaForTesting() {
void InstallMallocHooks(size_t max_allocated_pages, void InstallMallocHooks(size_t max_allocated_pages,
size_t num_metadata, size_t num_metadata,
size_t total_pages, size_t total_pages,
size_t sampling_frequency) { size_t sampling_frequency,
GuardedPageAllocator::OutOfMemoryCallback callback) {
static crash_reporter::CrashKeyString<24> malloc_crash_key(kMallocCrashKey); static crash_reporter::CrashKeyString<24> malloc_crash_key(kMallocCrashKey);
gpa = new GuardedPageAllocator(); gpa = new GuardedPageAllocator();
gpa->Init(max_allocated_pages, num_metadata, total_pages, base::DoNothing(), gpa->Init(max_allocated_pages, num_metadata, total_pages, std::move(callback),
false); false);
malloc_crash_key.Set(gpa->GetCrashKey()); malloc_crash_key.Set(gpa->GetCrashKey());
sampling_state.Init(sampling_frequency); sampling_state.Init(sampling_frequency);
......
...@@ -9,16 +9,19 @@ ...@@ -9,16 +9,19 @@
#include "build/build_config.h" #include "build/build_config.h"
#include "components/gwp_asan/client/export.h" #include "components/gwp_asan/client/export.h"
#include "components/gwp_asan/client/guarded_page_allocator.h"
namespace gwp_asan { namespace gwp_asan {
namespace internal { namespace internal {
// Initialize the guarded allocator with the given parameters, and install the // Initialize the guarded allocator with the given parameters, and install the
// sampling malloc shims with the provided sampling frequency. // sampling malloc shims with the provided sampling frequency.
GWP_ASAN_EXPORT void InstallMallocHooks(size_t max_allocated_pages, GWP_ASAN_EXPORT void InstallMallocHooks(
size_t num_metadata, size_t max_allocated_pages,
size_t total_pages, size_t num_metadata,
size_t sampling_frequency); size_t total_pages,
size_t sampling_frequency,
GuardedPageAllocator::OutOfMemoryCallback callback);
} // namespace internal } // namespace internal
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <string> #include <string>
#include "base/allocator/allocator_shim.h" #include "base/allocator/allocator_shim.h"
#include "base/bind_helpers.h"
#include "base/process/process_metrics.h" #include "base/process/process_metrics.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/test/gtest_util.h" #include "base/test/gtest_util.h"
...@@ -68,7 +69,7 @@ class SamplingMallocShimsTest : public base::MultiProcessTest { ...@@ -68,7 +69,7 @@ class SamplingMallocShimsTest : public base::MultiProcessTest {
crash_reporter::InitializeCrashKeys(); crash_reporter::InitializeCrashKeys();
InstallMallocHooks(AllocatorState::kMaxMetadata, InstallMallocHooks(AllocatorState::kMaxMetadata,
AllocatorState::kMaxMetadata, AllocatorState::kMaxSlots, AllocatorState::kMaxMetadata, AllocatorState::kMaxSlots,
kSamplingFrequency); kSamplingFrequency, base::DoNothing());
} }
protected: protected:
......
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
#include "components/gwp_asan/client/sampling_partitionalloc_shims.h" #include "components/gwp_asan/client/sampling_partitionalloc_shims.h"
#include <algorithm> #include <algorithm>
#include <utility>
#include "base/allocator/partition_allocator/partition_alloc.h" #include "base/allocator/partition_allocator/partition_alloc.h"
#include "base/bind_helpers.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/partition_alloc_buildflags.h" #include "base/partition_alloc_buildflags.h"
#include "components/crash/core/common/crash_key.h" #include "components/crash/core/common/crash_key.h"
...@@ -67,14 +67,16 @@ GWP_ASAN_EXPORT GuardedPageAllocator& GetPartitionAllocGpaForTesting() { ...@@ -67,14 +67,16 @@ GWP_ASAN_EXPORT GuardedPageAllocator& GetPartitionAllocGpaForTesting() {
return *gpa; return *gpa;
} }
void InstallPartitionAllocHooks(size_t max_allocated_pages, void InstallPartitionAllocHooks(
size_t num_metadata, size_t max_allocated_pages,
size_t total_pages, size_t num_metadata,
size_t sampling_frequency) { size_t total_pages,
size_t sampling_frequency,
GuardedPageAllocator::OutOfMemoryCallback callback) {
static crash_reporter::CrashKeyString<24> pa_crash_key( static crash_reporter::CrashKeyString<24> pa_crash_key(
kPartitionAllocCrashKey); kPartitionAllocCrashKey);
gpa = new GuardedPageAllocator(); gpa = new GuardedPageAllocator();
gpa->Init(max_allocated_pages, num_metadata, total_pages, base::DoNothing(), gpa->Init(max_allocated_pages, num_metadata, total_pages, std::move(callback),
true); true);
pa_crash_key.Set(gpa->GetCrashKey()); pa_crash_key.Set(gpa->GetCrashKey());
sampling_state.Init(sampling_frequency); sampling_state.Init(sampling_frequency);
......
...@@ -8,14 +8,17 @@ ...@@ -8,14 +8,17 @@
#include <stddef.h> // for size_t #include <stddef.h> // for size_t
#include "components/gwp_asan/client/export.h" #include "components/gwp_asan/client/export.h"
#include "components/gwp_asan/client/guarded_page_allocator.h"
namespace gwp_asan { namespace gwp_asan {
namespace internal { namespace internal {
GWP_ASAN_EXPORT void InstallPartitionAllocHooks(size_t max_allocated_pages, GWP_ASAN_EXPORT void InstallPartitionAllocHooks(
size_t num_metadata, size_t max_allocated_pages,
size_t total_pages, size_t num_metadata,
size_t sampling_frequency); size_t total_pages,
size_t sampling_frequency,
GuardedPageAllocator::OutOfMemoryCallback callback);
} // namespace internal } // namespace internal
} // namespace gwp_asan } // namespace gwp_asan
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <string> #include <string>
#include "base/allocator/partition_allocator/partition_alloc.h" #include "base/allocator/partition_allocator/partition_alloc.h"
#include "base/bind_helpers.h"
#include "base/partition_alloc_buildflags.h" #include "base/partition_alloc_buildflags.h"
#include "base/process/process_metrics.h" #include "base/process/process_metrics.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
...@@ -53,9 +54,9 @@ class SamplingPartitionAllocShimsTest : public base::MultiProcessTest { ...@@ -53,9 +54,9 @@ class SamplingPartitionAllocShimsTest : public base::MultiProcessTest {
public: public:
static void multiprocessTestSetup() { static void multiprocessTestSetup() {
crash_reporter::InitializeCrashKeys(); crash_reporter::InitializeCrashKeys();
InstallPartitionAllocHooks(AllocatorState::kMaxMetadata, InstallPartitionAllocHooks(
AllocatorState::kMaxMetadata, AllocatorState::kMaxMetadata, AllocatorState::kMaxMetadata,
AllocatorState::kMaxSlots, kSamplingFrequency); AllocatorState::kMaxSlots, kSamplingFrequency, base::DoNothing());
} }
protected: protected:
......
...@@ -45441,6 +45441,28 @@ uploading your change for review. ...@@ -45441,6 +45441,28 @@ uploading your change for review.
</summary> </summary>
</histogram> </histogram>
<histogram base="true" name="GwpAsan.AllocatorOom.Malloc" units="allocations"
expires_after="M80">
<owner>vtsyrklevich@chromium.org</owner>
<owner>dynamic-tools@google.com</owner>
<summary>
Reports how many allocations it took for malloc GWP-ASan to OOM. Reported
the first time the allocator fails to consecutively allocate
GuardedPageAllocator::kOutOfMemoryCount allocations in a row.
</summary>
</histogram>
<histogram base="true" name="GwpAsan.AllocatorOom.PartitionAlloc"
units="allocations" expires_after="M80">
<owner>vtsyrklevich@chromium.org</owner>
<owner>dynamic-tools@google.com</owner>
<summary>
Reports how many allocations it took for PartitionAlloc GWP-ASan to OOM.
Reported the first time the allocator fails to consecutively allocate
GuardedPageAllocator::kOutOfMemoryCount allocations in a row.
</summary>
</histogram>
<histogram name="GwpAsan.CrashAnalysisResult" enum="GwpAsanCrashAnalysisResult" <histogram name="GwpAsan.CrashAnalysisResult" enum="GwpAsanCrashAnalysisResult"
expires_after="M80"> expires_after="M80">
<owner>vtsyrklevich@chromium.org</owner> <owner>vtsyrklevich@chromium.org</owner>
...@@ -150794,6 +150816,17 @@ should be kept until we use this API. --> ...@@ -150794,6 +150816,17 @@ should be kept until we use this API. -->
<affected-histogram name="GwpAsan.CrashAnalysisResult"/> <affected-histogram name="GwpAsan.CrashAnalysisResult"/>
</histogram_suffixes> </histogram_suffixes>
<histogram_suffixes name="GwpAsanPerProcessOom" separator=".">
<suffix name="Browser" label="for the browser process."/>
<suffix name="Extension" label="for the extension process."/>
<suffix name="Gpu" label="for the gpu-process."/>
<suffix name="Ppapi" label="for the ppapi process."/>
<suffix name="Renderer" label="for the renderer process."/>
<suffix name="Utility" label="for the utility process."/>
<affected-histogram name="GwpAsan.AllocatorOom.Malloc"/>
<affected-histogram name="GwpAsan.AllocatorOom.PartitionAlloc"/>
</histogram_suffixes>
<histogram_suffixes name="GWSChromeJointExperiment" separator="_"> <histogram_suffixes name="GWSChromeJointExperiment" separator="_">
<suffix name="Experiment1" <suffix name="Experiment1"
label="Only page loads that are a result of a navigation from a web label="Only page loads that are a result of a navigation from a web
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment