1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
|
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/performance_manager/mechanisms/userspace_swap_chromeos.h"
#include <memory>
#include "base/files/scoped_file.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/page_size.h"
#include "base/posix/safe_strerror.h"
#include "base/threading/scoped_blocking_call.h"
#include "chromeos/ash/components/memory/userspace_swap/region.h"
#include "chromeos/ash/components/memory/userspace_swap/swap_storage.h"
#include "chromeos/ash/components/memory/userspace_swap/userfaultfd.h"
#include "chromeos/ash/components/memory/userspace_swap/userspace_swap.h"
#include "chromeos/ash/components/memory/userspace_swap/userspace_swap.mojom.h"
#include "components/performance_manager/public/graph/graph.h"
#include "components/performance_manager/public/graph/node_attached_data.h"
#include "components/performance_manager/public/graph/process_node.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/public/render_process_host_proxy.h"
#include "content/public/browser/render_process_host.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
namespace performance_manager {
namespace mechanism {
namespace userspace_swap {
namespace {
using ::ash::memory::userspace_swap::Region;
using ::ash::memory::userspace_swap::RendererSwapData;
using ::ash::memory::userspace_swap::SwapFile;
using ::ash::memory::userspace_swap::UserfaultFD;
using ::ash::memory::userspace_swap::UserspaceSwapConfig;
// We cache the swap device free space so we don't hammer the FS layer with
// unnecessary syscalls. The initial value of 30s was chosen because it seemed
// like a safe value that would prevent hitting the disk too frequently while
// preventing space from getting too low in times of heavy swap. Feel free to
// change it if you find a better value.
constexpr base::TimeDelta kSwapDeviceAvailableSpaceCheckInterval =
base::Seconds(30);
base::TimeTicks g_last_swap_device_free_space_check;
uint64_t g_swap_device_free_swap_bytes;
// UserspaceSwapMechanismData contains process node specific details and
// handles.
class UserspaceSwapMechanismData
: public ExternalNodeAttachedDataImpl<UserspaceSwapMechanismData> {
public:
explicit UserspaceSwapMechanismData(const ProcessNode* node) {}
~UserspaceSwapMechanismData() override = default;
std::unique_ptr<RendererSwapData> swap_data;
};
void InitializeProcessNodeOnGraph(int render_process_host_id,
base::ScopedFD uffd,
Region swap_area) {
// Now look up the ProcessNode so we can complete initialization.
DCHECK(uffd.is_valid());
base::WeakPtr<ProcessNode> process_node =
PerformanceManager::GetProcessNodeForRenderProcessHostId(
RenderProcessHostId(render_process_host_id));
if (!process_node) {
LOG(ERROR) << "Couldn't find process node for rphid: "
<< render_process_host_id;
return;
}
if (UserspaceSwapMechanismData::Destroy(process_node.get())) {
LOG(ERROR) << "ProcessNode contained UserspaceSwapMechanismData";
return;
}
auto* data = UserspaceSwapMechanismData::GetOrCreate(process_node.get());
// If all other setup has completed successfully, we can tell the renderer to
// construct an implementation of userspace_swap::mojom::UserspaceSwap.
mojo::PendingRemote<::userspace_swap::mojom::UserspaceSwap> remote;
const RenderProcessHostProxy& proxy =
process_node->GetRenderProcessHostProxy();
content::RenderProcessHost* render_process_host = proxy.Get();
render_process_host->BindReceiver(remote.InitWithNewPipeAndPassReceiver());
// Wrap up the received userfaultfd into a UserfaultFD instance.
std::unique_ptr<UserfaultFD> userfaultfd =
UserfaultFD::WrapFD(std::move(uffd));
// The SwapFile is always encrypted but the compression layer is optional.
SwapFile::Type swap_type = SwapFile::Type::kEncrypted;
if (UserspaceSwapConfig::Get().use_compressed_swap_file) {
swap_type =
static_cast<SwapFile::Type>(swap_type | SwapFile::Type::kCompressed);
}
std::unique_ptr<SwapFile> swap_file = SwapFile::Create(swap_type);
if (!swap_file) {
PLOG(ERROR) << "Unable to complete userspace swap initialization failure "
"creating swap file";
// If we can't create a swap file, then we will bail freeing our resources.
UserspaceSwapMechanismData::Destroy(process_node.get());
return;
}
data->swap_data = RendererSwapData::Create(
render_process_host_id, process_node->GetProcessId(),
std::move(userfaultfd), std::move(swap_file), swap_area,
std::move(remote));
}
} // namespace
bool IsEligibleToSwap(const ProcessNode* process_node) {
if (!process_node->GetProcess().IsValid()) {
return false;
}
auto* data = UserspaceSwapMechanismData::Get(process_node);
if (!data || !data->swap_data) {
return false;
}
// Now let the implementation decide if swap should actually be allowed.
return data->swap_data->SwapAllowed();
}
uint64_t GetSwapDeviceFreeSpaceBytes() {
auto now_ticks = base::TimeTicks::Now();
if (now_ticks - g_last_swap_device_free_space_check >
kSwapDeviceAvailableSpaceCheckInterval) {
g_last_swap_device_free_space_check = now_ticks;
g_swap_device_free_swap_bytes = SwapFile::GetBackingStoreFreeSpaceKB()
<< 10; // convert to bytes.
}
return g_swap_device_free_swap_bytes;
}
uint64_t GetProcessNodeSwapFileUsageBytes(const ProcessNode* process_node) {
auto* data = UserspaceSwapMechanismData::Get(process_node);
if (!data || !data->swap_data) {
return 0;
}
return data->swap_data->SwapDiskspaceUsedBytes();
}
uint64_t GetProcessNodeReclaimedBytes(const ProcessNode* process_node) {
auto* data = UserspaceSwapMechanismData::Get(process_node);
if (!data || !data->swap_data) {
return 0;
}
return data->swap_data->ReclaimedBytes();
}
uint64_t GetTotalSwapFileUsageBytes() {
return ash::memory::userspace_swap::GetGlobalSwapDiskspaceUsed();
}
uint64_t GetTotalReclaimedBytes() {
return ash::memory::userspace_swap::GetGlobalMemoryReclaimed();
}
void SwapProcessNode(const ProcessNode* process_node) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::WILL_BLOCK);
auto* data = UserspaceSwapMechanismData::Get(process_node);
if (!data || !data->swap_data) {
return;
}
auto& swap_data = data->swap_data;
// SwapProcessNode always starts by determining exactly how many regions we
// can swap based on current swapfile usage for this renderer and globally.
static const size_t kPageSize = base::GetPageSize();
static const uint64_t kPagesPerRegion =
UserspaceSwapConfig::Get().number_of_pages_per_region;
static const uint64_t kRegionSize = kPagesPerRegion * kPageSize;
const auto& config = UserspaceSwapConfig::Get();
uint64_t swap_file_disk_space_used_bytes =
swap_data->SwapDiskspaceUsedBytes();
// This renderer can only swap up to what's available in the global swap file
// limit or what's available in it's own swap file limit.
int64_t available_swap_bytes =
std::min(config.maximum_swap_disk_space_bytes -
ash::memory::userspace_swap::GetGlobalSwapDiskspaceUsed(),
config.renderer_maximum_disk_swap_file_size_bytes -
swap_file_disk_space_used_bytes);
// We have a configurable limit to the number of regions we will consider per
// iteration and adjust based on how much disk space is actually
// available for us which was calculated before.
// Finally, we know how many regions this renderer is able to swap.
int64_t available_swap_regions = available_swap_bytes / kRegionSize;
int64_t total_regions_swapable =
std::min(static_cast<int64_t>(config.renderer_region_limit_per_swap),
available_swap_regions);
if (total_regions_swapable <= 0) {
// We don't have enough space available to swap a single region.
return;
}
// Now we know how many regions this renderer can theoretically swap after
// enforcing all configurable limits.
ash::memory::userspace_swap::SwapRenderer(
swap_data.get(), total_regions_swapable * kRegionSize);
}
UserspaceSwapInitializationImpl::UserspaceSwapInitializationImpl(
int render_process_host_id)
: render_process_host_id_(render_process_host_id) {
CHECK(UserspaceSwapInitializationImpl::UserspaceSwapSupportedAndEnabled());
}
UserspaceSwapInitializationImpl::~UserspaceSwapInitializationImpl() = default;
// static
bool UserspaceSwapInitializationImpl::UserspaceSwapSupportedAndEnabled() {
return ash::memory::userspace_swap::UserspaceSwapSupportedAndEnabled();
}
// static
void UserspaceSwapInitializationImpl::Create(
int render_process_host_id,
mojo::PendingReceiver<::userspace_swap::mojom::UserspaceSwapInitialization>
receiver) {
auto impl =
std::make_unique<UserspaceSwapInitializationImpl>(render_process_host_id);
mojo::MakeSelfOwnedReceiver(std::move(impl), std::move(receiver));
}
void UserspaceSwapInitializationImpl::TransferUserfaultFD(
uint64_t uffd_error,
mojo::PlatformHandle uffd_handle,
uint64_t mmap_error,
MemoryRegionPtr swap_area,
TransferUserfaultFDCallback cb) {
base::ScopedClosureRunner scr(std::move(cb));
if (received_transfer_cb_) {
return;
}
received_transfer_cb_ = true;
if (uffd_error != 0) {
LOG(ERROR) << "Unable to create userfaultfd for renderer: "
<< base::safe_strerror(uffd_error);
return;
}
if (mmap_error != 0) {
LOG(ERROR) << "Unable to create memory area for renderer: "
<< base::safe_strerror(mmap_error);
return;
}
if (!uffd_handle.is_valid()) {
LOG(ERROR) << "FD received is invalid.";
return;
}
InitializeProcessNodeOnGraph(render_process_host_id_, uffd_handle.TakeFD(),
Region(swap_area->address, swap_area->length));
}
} // namespace userspace_swap
} // namespace mechanism
} // namespace performance_manager
|