File: userspace_swap_policy_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 (342 lines) | stat: -rw-r--r-- 12,019 bytes parent folder | download | duplicates (5)
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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// 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/policies/userspace_swap_policy_chromeos.h"

#include "base/system/sys_info.h"
#include "base/time/time.h"
#include "chrome/browser/performance_manager/mechanisms/userspace_swap_chromeos.h"
#include "chromeos/ash/components/memory/userspace_swap/swap_storage.h"
#include "chromeos/ash/components/memory/userspace_swap/userspace_swap.h"
#include "components/performance_manager/public/graph/frame_node.h"
#include "components/performance_manager/public/graph/node_attached_data.h"
#include "components/performance_manager/public/graph/page_node.h"
#include "components/performance_manager/public/graph/process_node.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"

namespace performance_manager {
namespace policies {

namespace {
using ::ash::memory::userspace_swap::SwapFile;
using ::ash::memory::userspace_swap::UserspaceSwapConfig;

class UserspaceSwapPolicyData
    : public ExternalNodeAttachedDataImpl<UserspaceSwapPolicyData> {
 public:
  explicit UserspaceSwapPolicyData(const ProcessNode* node) {}
  ~UserspaceSwapPolicyData() override = default;

  static UserspaceSwapPolicyData* EnsureForProcess(
      const ProcessNode* process_node) {
    return UserspaceSwapPolicyData::GetOrCreate(process_node);
  }

  bool initialization_attempted_ = false;
  bool process_initialized_ = false;
  base::TimeTicks last_swap_;
};

constexpr base::TimeDelta kMetricsInterval = base::Seconds(30);

}  // namespace

UserspaceSwapPolicy::UserspaceSwapPolicy(const UserspaceSwapConfig& config)
    : config_(config) {
  // To avoid failures related to chromeos-linux, we validate that we're running
  // on chromeos before enforcing the following check.
  if (base::SysInfo::IsRunningOnChromeOS()) {
    DCHECK(UserspaceSwapPolicy::UserspaceSwapSupportedAndEnabled());
  }

  if (VLOG_IS_ON(1) && !metrics_timer_->IsRunning()) {
    metrics_timer_->Start(
        FROM_HERE, kMetricsInterval,
        base::BindRepeating(&UserspaceSwapPolicy::PrintAllSwapMetrics,
                            weak_factory_.GetWeakPtr()));
  }
}

UserspaceSwapPolicy::UserspaceSwapPolicy()
    : UserspaceSwapPolicy(UserspaceSwapConfig::Get()) {}

UserspaceSwapPolicy::~UserspaceSwapPolicy() = default;

void UserspaceSwapPolicy::OnPassedToGraph(Graph* graph) {
  graph->AddProcessNodeObserver(this);

  // Only handle the memory pressure notifications if the feature to swap on
  // moderate pressure is enabled.
  if (config_->swap_on_moderate_pressure) {
    graph->AddSystemNodeObserver(this);
  }
}

void UserspaceSwapPolicy::OnTakenFromGraph(Graph* graph) {
  if (config_->swap_on_moderate_pressure) {
    graph->RemoveSystemNodeObserver(this);
  }

  graph->RemoveProcessNodeObserver(this);
}

void UserspaceSwapPolicy::OnAllFramesInProcessFrozen(
    const ProcessNode* process_node) {
  if (config_->swap_on_freeze) {
    // We don't provide a page node because the visibility requirements don't
    // matter on freeze.
    if (IsEligibleToSwap(process_node, nullptr)) {
      VLOG(1) << "rphid: " << process_node->GetRenderProcessHostId()
              << " pid: " << process_node->GetProcessId() << " swap on freeze";
      UserspaceSwapPolicyData::EnsureForProcess(process_node)->last_swap_ =
          base::TimeTicks::Now();
      SwapProcessNode(process_node);
    }
  }
}

void UserspaceSwapPolicy::OnProcessNodeAdded(const ProcessNode* process_node) {
  // If data was still associated with this node make sure it's blown away and
  // any existing file descriptors are closed.
  if (UserspaceSwapPolicyData::Destroy(process_node)) {
    DLOG(FATAL)
        << "ProcessNode had a UserspaceSwapPolicyData attached when added.";
  }
}

bool UserspaceSwapPolicy::InitializeProcessNode(
    const ProcessNode* process_node) {
  // TODO(bgeffon): Add policy specific initialization or remove once final CLs
  // land.
  return true;
}

void UserspaceSwapPolicy::OnProcessLifetimeChange(
    const ProcessNode* process_node) {
  if (!process_node->GetProcess().IsValid()) {
    return;
  }

  UserspaceSwapPolicyData* data =
      UserspaceSwapPolicyData::EnsureForProcess(process_node);
  if (!data->initialization_attempted_) {
    data->initialization_attempted_ = true;

    // If this fails we don't attempt swap ever.
    data->process_initialized_ = InitializeProcessNode(process_node);

    LOG_IF(ERROR, !data->process_initialized_)
        << "Unable to initialize process node";
  }
}

base::TimeTicks UserspaceSwapPolicy::GetLastSwapTime(
    const ProcessNode* process_node) {
  return UserspaceSwapPolicyData::EnsureForProcess(process_node)->last_swap_;
}

void UserspaceSwapPolicy::OnMemoryPressure(
    base::MemoryPressureListener::MemoryPressureLevel new_level) {
  if (new_level == base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE) {
    return;
  }

  auto now_ticks = base::TimeTicks::Now();
  // Try not to walk the graph too frequently because we can receive moderate
  // memory pressure notifications every 10s.
  if (now_ticks - last_graph_walk_ < config_->graph_walk_frequency) {
    return;
  }

  last_graph_walk_ = now_ticks;
  SwapNodesOnGraph();
}

void UserspaceSwapPolicy::SwapNodesOnGraph() {
  for (const PageNode* page_node : GetOwningGraph()->GetAllPageNodes()) {
    // Check that we have a main frame.
    const FrameNode* main_frame_node = page_node->GetMainFrameNode();
    if (!main_frame_node)
      continue;

    const ProcessNode* process_node = main_frame_node->GetProcessNode();
    if (IsEligibleToSwap(process_node, page_node)) {
      VLOG(1) << "rphid: " << process_node->GetRenderProcessHostId()
              << " pid: " << process_node->GetProcessId()
              << " trigger swap for frame " << main_frame_node->GetURL();
      UserspaceSwapPolicyData::EnsureForProcess(process_node)->last_swap_ =
          base::TimeTicks::Now();
      SwapProcessNode(process_node);
    }
  }
}

void UserspaceSwapPolicy::PrintAllSwapMetrics() {
  uint64_t total_reclaimed = 0;
  uint64_t total_on_disk = 0;
  uint64_t total_renderers = 0;
  for (const PageNode* page_node : GetOwningGraph()->GetAllPageNodes()) {
    const FrameNode* main_frame_node = page_node->GetMainFrameNode();
    if (!main_frame_node) {
      continue;
    }

    const ProcessNode* process_node = main_frame_node->GetProcessNode();

    auto now_ticks = base::TimeTicks::Now();
    if (process_node && process_node->GetProcess().IsValid()) {
      bool is_visible = page_node->IsVisible();
      auto last_visibility_change =
          now_ticks - page_node->GetLastVisibilityChangeTime();
      auto url = main_frame_node->GetURL();

      uint64_t memory_reclaimed = GetProcessNodeReclaimedBytes(process_node);
      uint64_t disk_space_used = GetProcessNodeSwapFileUsageBytes(process_node);
      total_on_disk += disk_space_used;
      total_reclaimed += memory_reclaimed;
      total_renderers++;

      VLOG(1) << "Frame " << url << " visibile: " << is_visible
              << " last_chg: " << last_visibility_change
              << " last_swap: " << (now_ticks - GetLastSwapTime(process_node))
              << " reclaimed: " << (memory_reclaimed >> 10) << "Kb"
              << " on disk: " << (disk_space_used >> 10) << "Kb";
    }
  }

  VLOG(1) << "Swap Summary, Renderers: " << total_renderers
          << " reclaimed: " << (total_reclaimed >> 10)
          << "Kb, total on disk: " << (total_on_disk >> 10) << "Kb"
          << " Backing Store free space: "
          << (GetSwapDeviceFreeSpaceBytes() >> 10) << "Kb";
}

void UserspaceSwapPolicy::SwapProcessNode(const ProcessNode* process_node) {
  performance_manager::mechanism::userspace_swap::SwapProcessNode(process_node);
}

uint64_t UserspaceSwapPolicy::GetProcessNodeSwapFileUsageBytes(
    const ProcessNode* process_node) {
  return performance_manager::mechanism::userspace_swap::
      GetProcessNodeSwapFileUsageBytes(process_node);
}

uint64_t UserspaceSwapPolicy::GetProcessNodeReclaimedBytes(
    const ProcessNode* process_node) {
  return performance_manager::mechanism::userspace_swap::
      GetProcessNodeReclaimedBytes(process_node);
}

uint64_t UserspaceSwapPolicy::GetTotalSwapFileUsageBytes() {
  return performance_manager::mechanism::userspace_swap::
      GetTotalSwapFileUsageBytes();
}

uint64_t UserspaceSwapPolicy::GetSwapDeviceFreeSpaceBytes() {
  return performance_manager::mechanism::userspace_swap::
      GetSwapDeviceFreeSpaceBytes();
}

bool UserspaceSwapPolicy::IsPageNodeLoadingOrBusy(const PageNode* page_node) {
  const PageNode::LoadingState loading_state = page_node->GetLoadingState();
  return loading_state == PageNode::LoadingState::kLoading ||
         loading_state == PageNode::LoadingState::kLoadedBusy;
}

bool UserspaceSwapPolicy::IsPageNodeAudible(const PageNode* page_node) {
  return page_node->IsAudible();
}

bool UserspaceSwapPolicy::IsPageNodeVisible(const PageNode* page_node) {
  return page_node->IsVisible();
}

base::TimeTicks UserspaceSwapPolicy::GetLastVisibilityChangeTime(
    const PageNode* page_node) {
  return page_node->GetLastVisibilityChangeTime();
}

bool UserspaceSwapPolicy::IsEligibleToSwap(const ProcessNode* process_node,
                                           const PageNode* page_node) {
  if (!process_node || !process_node->GetProcess().IsValid()) {
    LOG(ERROR) << "Process node not valid";
    return false;
  }

  auto* data = UserspaceSwapPolicyData::EnsureForProcess(process_node);
  if (!data->process_initialized_) {
    return false;
  }

  // Always check with the mechanism to make sure that it can still be swapped
  // and that nothing unexpected has happened.
  if (!performance_manager::mechanism::userspace_swap::IsEligibleToSwap(
          process_node)) {
    return false;
  }

  auto now_ticks = base::TimeTicks::Now();
  // Don't swap a renderer too frequently.
  auto time_since_last_swap = now_ticks - GetLastSwapTime(process_node);
  if (time_since_last_swap < config_->process_swap_frequency) {
    return false;
  }

  // If the caller provided a PageNode we will validate the visibility state of
  // it.
  if (page_node) {
    // If we're loading, audible, or visible we will not swap.
    if (IsPageNodeLoadingOrBusy(page_node) || IsPageNodeVisible(page_node) ||
        IsPageNodeAudible(page_node)) {
      return false;
    }

    // Next the page node must have been invisible for longer than the
    // configured time.
    if ((now_ticks - GetLastVisibilityChangeTime(page_node)) <
        config_->invisible_time_before_swap) {
      return false;
    }
  }

  // To avoid hammering the system with fstat(2) system calls we will cache the
  // available disk space for 30 seconds. But we only check if it's been
  // configured to enforce a swap device minimum.
  if (config_->minimum_swap_disk_space_available > 0) {
    // Check if we can't swap because the device is running low on space.
    if (GetSwapDeviceFreeSpaceBytes() <
        config_->minimum_swap_disk_space_available) {
      return false;
    }
  }

  // Make sure we're not exceeding the total swap file usage across all
  // renderers.
  if (config_->maximum_swap_disk_space_bytes > 0) {
    if (GetTotalSwapFileUsageBytes() >=
        config_->maximum_swap_disk_space_bytes) {
      return false;
    }
  }

  // And make sure we're not exceeding the per-renderer swap file limit.
  if (config_->renderer_maximum_disk_swap_file_size_bytes > 0) {
    if (GetProcessNodeSwapFileUsageBytes(process_node) >=
        config_->renderer_maximum_disk_swap_file_size_bytes) {
      return false;
    }
  }

  return true;
}

// Static
bool UserspaceSwapPolicy::UserspaceSwapSupportedAndEnabled() {
  return ash::memory::userspace_swap::UserspaceSwapSupportedAndEnabled();
}

}  // namespace policies
}  // namespace performance_manager