File: userspace_swap_chromeos.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (284 lines) | stat: -rw-r--r-- 10,343 bytes parent folder | download | duplicates (6)
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