Commit debbe86e authored by Brian Geffon's avatar Brian Geffon Committed by Commit Bot

CrOS: userspace swap: Add the userspace swap tunables

To keep CLs small on userspace swap this CL only adds the configuration
tunables which will be available to userspace swap.

Additionally, it adds a helper to determine if the kernel supports the
necessary features for userspace swap.

BUG=chromium:1067833

Change-Id: Id674abede3d503218189cd3b4bef130c4297b5c9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2288492
Commit-Queue: Brian Geffon <bgeffon@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#786468}
parent 00309af6
...@@ -43,6 +43,8 @@ component("memory") { ...@@ -43,6 +43,8 @@ component("memory") {
"userspace_swap/swap_storage.h", "userspace_swap/swap_storage.h",
"userspace_swap/userfaultfd.cc", "userspace_swap/userfaultfd.cc",
"userspace_swap/userfaultfd.h", "userspace_swap/userfaultfd.h",
"userspace_swap/userspace_swap.cc",
"userspace_swap/userspace_swap.h",
] ]
} }
......
// Copyright 2020 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 "chromeos/memory/userspace_swap/userspace_swap.h"
#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/metrics/field_trial_params.h"
#include "base/process/process_metrics.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chromeos/memory/userspace_swap/region.h"
#include "chromeos/memory/userspace_swap/swap_storage.h"
#include "chromeos/memory/userspace_swap/userfaultfd.h"
namespace chromeos {
namespace memory {
namespace userspace_swap {
namespace {
// NOTE: Descriptions for these feature params can be found in the userspace
// swap header file for the UserspaceSwapConfig struct.
const base::Feature kUserspaceSwap{"UserspaceSwapEnabled",
base::FEATURE_DISABLED_BY_DEFAULT};
const base::FeatureParam<int> kUserspaceSwapPagesPerRegion = {
&kUserspaceSwap, "UserspaceSwapPagesPerRegion", 16};
const base::FeatureParam<int> kUserspaceSwapVMARegionMinSizeKB = {
&kUserspaceSwap, "UserspaceSwapVMARegionMinSizeKB", 1024};
const base::FeatureParam<int> kUserspaceSwapVMARegionMaxSizeKB = {
&kUserspaceSwap, "UserspaceSwapVMARegionMaxSizeKB",
1024 * 256 /* 256 MB */};
const base::FeatureParam<bool> kUserspaceSwapCompressedSwapFile = {
&kUserspaceSwap, "UserspaceSwapCompressedSwapFile", true};
const base::FeatureParam<int> kUserspaceSwapMinSwapDeviceSpaceAvailMB = {
&kUserspaceSwap, "UserspaceSwapMinSwapDeviceSpaceAvailMB", 128};
const base::FeatureParam<int> kUserspaceSwapMaximumSwapDiskSpaceMB = {
&kUserspaceSwap, "UserspaceSwapMaximumSwapSpaceMB", 1024};
const base::FeatureParam<int> kUserspaceSwapRendererMaximumSwapDiskSpaceMB = {
&kUserspaceSwap, "UserspaceSwapRendererMaximumSwapSpaceMB", 128};
const base::FeatureParam<int> kUserspaceSwapRendererRegionLimitPerSwap = {
&kUserspaceSwap, "UserspaceSwapRendererRegionLimitPerSwap", 100};
const base::FeatureParam<int> kUserspaceSwapBlockedRefaultTimeSec = {
&kUserspaceSwap, "UserspaceSwapBlockedRefaultTimeSec", 45};
const base::FeatureParam<int>
kUserspaceSwapModeratePressureGraphWalkFrequencySec = {
&kUserspaceSwap, "UserspaceSwapModeratePressureGraphWalkFrequencySec",
60};
const base::FeatureParam<int> kUserspaceSwapProcessSwapFrequencySec = {
&kUserspaceSwap, "UserspaceSwapProcessSwapFrequencySec", 120};
const base::FeatureParam<int> kUserspaceSwapInvisibleTimeBeforeSwapSec = {
&kUserspaceSwap, "UserspaceSwapInvisibleTimeBeforeSwapSec", 60};
const base::FeatureParam<bool> kUserspaceDoSwapModeratePressure = {
&kUserspaceSwap, "UserspaceSwapDoSwapOnModeratePressure", true};
const base::FeatureParam<bool> kUserspaceDoSwapOnFreeze = {
&kUserspaceSwap, "UserspaceSwapDoSwapOnFreeze", true};
const base::FeatureParam<bool> kUserspaceSwapShuffleMapsOrder = {
&kUserspaceSwap, "UserspaceSwapSuffleMapsOrder", true};
} // namespace
UserspaceSwapConfig::UserspaceSwapConfig() = default;
UserspaceSwapConfig::UserspaceSwapConfig(const UserspaceSwapConfig& other) =
default;
// Static
CHROMEOS_EXPORT const UserspaceSwapConfig& UserspaceSwapConfig::Get() {
static UserspaceSwapConfig config = []() -> UserspaceSwapConfig {
UserspaceSwapConfig config = {};
// Populate the config object.
config.enabled = base::FeatureList::IsEnabled(kUserspaceSwap);
config.number_of_pages_per_region = kUserspaceSwapPagesPerRegion.Get();
config.vma_region_minimum_size_bytes =
kUserspaceSwapVMARegionMinSizeKB.Get() << 10; // Convert KB to bytes.
config.vma_region_maximum_size_bytes =
kUserspaceSwapVMARegionMaxSizeKB.Get() << 10; // Convert KB to bytes.
config.use_compressed_swap_file = kUserspaceSwapCompressedSwapFile.Get();
config.minimum_swap_disk_space_available =
kUserspaceSwapMinSwapDeviceSpaceAvailMB.Get()
<< 20; // Convert MB to bytes.
config.maximum_swap_disk_space_bytes =
kUserspaceSwapMaximumSwapDiskSpaceMB.Get()
<< 20; // Convert MB to bytes.
config.renderer_maximum_disk_swap_file_size_bytes =
kUserspaceSwapRendererMaximumSwapDiskSpaceMB.Get()
<< 20; // Convert MB to bytes.
config.renderer_region_limit_per_swap =
kUserspaceSwapRendererRegionLimitPerSwap.Get();
config.blocked_refault_time =
base::TimeDelta::FromSeconds(kUserspaceSwapBlockedRefaultTimeSec.Get());
config.graph_walk_frequency = base::TimeDelta::FromSeconds(
kUserspaceSwapModeratePressureGraphWalkFrequencySec.Get());
config.process_swap_frequency = base::TimeDelta::FromSeconds(
kUserspaceSwapProcessSwapFrequencySec.Get());
config.invisible_time_before_swap = base::TimeDelta::FromSeconds(
kUserspaceSwapInvisibleTimeBeforeSwapSec.Get());
config.swap_on_moderate_pressure = kUserspaceDoSwapModeratePressure.Get();
config.swap_on_freeze = kUserspaceDoSwapOnFreeze.Get();
config.shuffle_maps_on_swap = kUserspaceSwapShuffleMapsOrder.Get();
return config;
}();
return config;
}
// An operator<< to allow us to print the values of a UserspaceSwapConfig to a
// stream.
std::ostream& operator<<(std::ostream& out, const UserspaceSwapConfig& c) {
out << "UserspaceSwapConfig enabled: " << c.enabled << "\n";
if (c.enabled) {
out << "number_of_pages_per_region: " << c.number_of_pages_per_region
<< "\n";
out << "vma_region_minimum_size_bytes: " << c.vma_region_minimum_size_bytes
<< "\n";
out << "vma_region_maximum_size_bytes: " << c.vma_region_maximum_size_bytes
<< "\n";
out << "use_compressed_swap: " << c.use_compressed_swap_file << "\n";
out << "minimum_swap_disk_space_available: "
<< c.minimum_swap_disk_space_available << "\n";
out << "maximum_swap_disk_space_bytes: " << c.maximum_swap_disk_space_bytes
<< "\n";
out << "renderer_maximum_disk_swap_file_size_bytes: "
<< c.renderer_maximum_disk_swap_file_size_bytes << "\n";
out << "renderer_region_limit_per_swap: "
<< c.renderer_region_limit_per_swap << "\n";
out << "blocked_refault_time: " << c.blocked_refault_time << "\n";
out << "graph_walk_frequency: " << c.graph_walk_frequency << "\n";
out << "process_swap_frequency: " << c.process_swap_frequency << "\n";
out << "invisible_time_before_swap: " << c.invisible_time_before_swap
<< "\n";
out << "swap_on_freeze: " << c.swap_on_freeze << "\n";
out << "swap_on_moderate_pressure: " << c.swap_on_moderate_pressure << "\n";
out << "shuffle_maps_on_swap: " << c.shuffle_maps_on_swap << "\n";
}
return out;
}
// KernelSupportsUserspaceSwap will test for all features necessary to enbable
// userspace swap.
CHROMEOS_EXPORT bool KernelSupportsUserspaceSwap() {
static bool userfault_fd_supported = chromeos::memory::userspace_swap::
UserfaultFD::KernelSupportsUserfaultFD();
// We also need to make sure the kernel supports the mremap operation with
// MREMAP_DONTUNMAP.
static bool mremap_dontunmap_supported = []() -> bool {
const size_t allocation_size = base::GetPageSize();
void* source_mapping = mmap(NULL, allocation_size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (source_mapping == MAP_FAILED) {
return false;
}
// This simple remap should only fail if MREMAP_DONTUNMAP isn't
// supported.
void* dest_mapping =
mremap(source_mapping, allocation_size, allocation_size,
MREMAP_DONTUNMAP | MREMAP_MAYMOVE, 0);
if (dest_mapping == MAP_FAILED) {
munmap(source_mapping, allocation_size);
return false;
}
munmap(dest_mapping, allocation_size);
munmap(source_mapping, allocation_size);
return true;
}();
return userfault_fd_supported && mremap_dontunmap_supported;
}
} // namespace userspace_swap
} // namespace memory
} // namespace chromeos
// Copyright 2020 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 CHROMEOS_MEMORY_USERSPACE_SWAP_USERSPACE_SWAP_H_
#define CHROMEOS_MEMORY_USERSPACE_SWAP_USERSPACE_SWAP_H_
#include <sys/mman.h>
#include <cstdint>
#include "base/time/time.h"
#include "chromeos/chromeos_export.h"
#include "chromeos/memory/userspace_swap/region.h"
#ifndef MREMAP_DONTUNMAP
#define MREMAP_DONTUNMAP 4
#endif
// This file is for containing the browser and renderer common userspace swap
// components such as helper functions and structures.
namespace chromeos {
namespace memory {
namespace userspace_swap {
// UserspaceSwapConfig is a structure which contains all configuration values
// for userspace swap.
struct CHROMEOS_EXPORT UserspaceSwapConfig {
UserspaceSwapConfig();
UserspaceSwapConfig(const UserspaceSwapConfig& other);
// Returns the Current UserspaceSwapConfig.
static const UserspaceSwapConfig& Get();
friend std::ostream& operator<<(std::ostream& out,
const UserspaceSwapConfig& c);
// enabled is true if the userspace swap feature is enabled.
bool enabled;
// Number of pages per region represents the number of pages we will use for
// each chunk we attempt to swap at a time.
uint16_t number_of_pages_per_region;
// VMA region minimum size represents the minimum size that a VMA must be to
// be considered for userspace swap.
uint64_t vma_region_minimum_size_bytes;
// VMA region maximum size represents the maximum size a VMA can be to be
// considered for userspace swap. The reason we have a maximum is because it's
// very common to have LARGE sparse VMAs, which can be 1+GB and used primarily
// for the on demand page faulted nature of anonymous mappings on linux.
// Examples of this are how the JVM and others will allocate large several GB
// heaps in one go. It would be silly to walk the entire thing checking if
// pages are in core, a good value here is probably less than 256MB.
uint64_t vma_region_maximum_size_bytes;
// If true the swap file will be compressed on disk.
bool use_compressed_swap_file;
// Minimum disk space swap available is the lower limit of free disk space on
// the swap device. If the available space on the device backing storage
// is lower than this value no further swapping is allowed, this prevents
// userspace swap from exhausting disk space.
uint64_t minimum_swap_disk_space_available;
// Maximum swap disk space represents the maxmium disk space userspace swap is
// allowed to use across all renderers.
uint64_t maximum_swap_disk_space_bytes;
// Renderer maximum disk file size represents the maximum size a swap file may
// for an individual renderer.
uint64_t renderer_maximum_disk_swap_file_size_bytes;
// Renderer region limit per swap limits the number of regions that that an
// individual renderer can swap on each swap, note that each region is
// configured by the number of pages per region so these two together limit
// the total number of pages per swap round of a process.
uint32_t renderer_region_limit_per_swap;
// The blocked refault time is the minimum time a region must be swapped out
// without being blocked. This prevents disk thrashing where if a region
// is immediately refaulted we don't want to swap it again as it'll likely be
// needed in the future, for example, if a region has a blocked refault time
// of 30s if it is refaulted in less than 30s it will never be swapped again.
base::TimeDelta blocked_refault_time;
// Graph walk frequency represents the (shortest) duration in which you can
// walk the graph, that is, a graph walk frequency of 60s means that you will
// not walk the graph more than once every 60s.
base::TimeDelta graph_walk_frequency;
// The process Swap frequency limits the frequency on which a process may be
// swapped, for example 60s means that a process will not be swapped more than
// once every 60s.
base::TimeDelta process_swap_frequency;
// Invisible Time Before Swap is the amount of time a renderer must be
// invisible before it can be considered for swap.
base::TimeDelta invisible_time_before_swap;
// Swap on freeze, if true will swap a process when all frames are frozen.
bool swap_on_freeze;
// Swap on moderate pressure will walk the graph (based on the frequency of
// graph walk frequency) looking for renderers to swap based on visibility
// state.
bool swap_on_moderate_pressure;
// Shuffle maps order will randomly shuffle the processes maps ordering before
// swapping, it does this so that subsequent swaps can start from different
// memory regions.
bool shuffle_maps_on_swap;
};
// Returns true if the kernel supports all the features necessary for userspace
// swap. These features are userfaultfd(2) and mremap(2) with MREMAP_DONTUNMAP
// support this method is the source of truth for the browser UserspaceSwap and
// the rendererer UserspaceSwapImpl.
CHROMEOS_EXPORT bool KernelSupportsUserspaceSwap();
} // namespace userspace_swap
} // namespace memory
} // namespace chromeos
#endif // CHROMEOS_MEMORY_USERSPACE_SWAP_USERSPACE_SWAP_H_
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