blob: 92ff9b5347531bc2dc84f32d0e84168303ef909d [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 "third_party/blink/public/common/experiments/memory_ablation_experiment.h"
#include <algorithm>
#include <limits>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/debug/alias.h"
#include "base/metrics/field_trial_params.h"
#include "base/process/process_metrics.h"
#include "base/rand_util.h"
#include "base/sequenced_task_runner.h"
#include "base/system/sys_info.h"
namespace blink {
const base::Feature kMemoryAblationFeature{"MemoryAblation",
base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kRendererMemoryAblationFeature{
"RendererMemoryAblation", base::FEATURE_DISABLED_BY_DEFAULT};
const char kMemoryAblationFeatureSizeParam[] = "Size";
const char kMemoryAblationFeatureMinRAMParam[] = "MinRAM";
const char kMemoryAblationFeatureMaxRAMParam[] = "MaxRAM";
// Since MaybeStart() is called during startup, we delay the allocation
// to avoid slowing things down.
constexpr size_t kAllocationDelaySeconds = 5;
// We need to touch allocated memory to "dirty" it. However, touching
// large chunks of memory (even a single byte per page) can be costly
// (~60ms per 10MiB on Nexus 5). So we touch in chunks to allow other
// things to happen in between. With the following values we'll touch
// 2MiB per second, spending ~10% of 250ms time slice per chunk.
constexpr size_t kTouchDelayMilliseconds = 250;
constexpr size_t kTouchChunkSize = 512 * 1024;
MemoryAblationExperiment::MemoryAblationExperiment() {}
MemoryAblationExperiment::~MemoryAblationExperiment() {}
MemoryAblationExperiment* MemoryAblationExperiment::GetInstance() {
static auto* instance = new MemoryAblationExperiment();
return instance;
}
void MemoryAblationExperiment::MaybeStart(
scoped_refptr<base::SequencedTaskRunner> task_runner) {
MaybeStartInternal(kMemoryAblationFeature, task_runner);
}
void MemoryAblationExperiment::MaybeStartForRenderer(
scoped_refptr<base::SequencedTaskRunner> task_runner) {
MaybeStartInternal(kRendererMemoryAblationFeature, task_runner);
}
void MemoryAblationExperiment::MaybeStartInternal(
const base::Feature& memory_ablation_feature,
scoped_refptr<base::SequencedTaskRunner> task_runner) {
int min_ram_mib = base::GetFieldTrialParamByFeatureAsInt(
memory_ablation_feature, kMemoryAblationFeatureMinRAMParam,
0 /* default value */);
int max_ram_mib = base::GetFieldTrialParamByFeatureAsInt(
memory_ablation_feature, kMemoryAblationFeatureMaxRAMParam,
std::numeric_limits<int>::max() /* default value */);
if (base::SysInfo::AmountOfPhysicalMemoryMB() > max_ram_mib ||
base::SysInfo::AmountOfPhysicalMemoryMB() <= min_ram_mib) {
return;
}
int size = base::GetFieldTrialParamByFeatureAsInt(
memory_ablation_feature, kMemoryAblationFeatureSizeParam,
0 /* default value */);
if (size > 0) {
GetInstance()->Start(task_runner, size);
}
}
void MemoryAblationExperiment::Start(
scoped_refptr<base::SequencedTaskRunner> task_runner,
size_t memory_size) {
DCHECK(!task_runner_) << "Already started";
task_runner_ = task_runner;
// This class is a singleton, so "Unretained(this)" below is fine.
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&MemoryAblationExperiment::AllocateMemory,
base::Unretained(this), memory_size),
base::TimeDelta::FromSeconds(kAllocationDelaySeconds));
}
void MemoryAblationExperiment::AllocateMemory(size_t size) {
memory_size_ = size;
memory_.reset(new uint8_t[size]);
ScheduleTouchMemory(0);
}
void MemoryAblationExperiment::TouchMemory(size_t offset) {
if (memory_) {
size_t page_size = base::GetPageSize();
uint8_t* memory = memory_.get();
size_t max_offset = std::min(offset + kTouchChunkSize, memory_size_);
if (offset == 0) {
// Fill the first page with random bytes. We'll use this page as
// a template for all other pages.
size_t rand_size = std::min(page_size, memory_size_);
base::RandBytes(memory, rand_size);
offset += rand_size;
}
for (; offset < max_offset; offset += page_size) {
// Copy from the first page.
size_t copy_size = std::min(page_size, max_offset - offset);
memcpy(memory + offset, memory, copy_size);
// Make this page different by stamping it with the offset.
if (copy_size >= sizeof(size_t)) {
*reinterpret_cast<size_t*>(memory + offset) = offset;
}
}
// Make sure compiler doesn't optimize away the writes.
base::debug::Alias(memory);
if (offset < memory_size_) {
ScheduleTouchMemory(offset);
}
}
}
void MemoryAblationExperiment::ScheduleTouchMemory(size_t offset) {
// This class is a singleton, so "Unretained(this)" below is fine.
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&MemoryAblationExperiment::TouchMemory,
base::Unretained(this), offset),
base::TimeDelta::FromMilliseconds(kTouchDelayMilliseconds));
}
} // namespace blink