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
|
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_PERFORMANCE_MANAGER_SCENARIO_API_PERFORMANCE_SCENARIOS_H_
#define COMPONENTS_PERFORMANCE_MANAGER_SCENARIO_API_PERFORMANCE_SCENARIOS_H_
#include <atomic>
#include <utility>
#include "base/component_export.h"
#include "base/containers/enum_set.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/shared_memory_safety_checker.h"
#include "base/memory/structured_shared_memory.h"
namespace performance_scenarios {
// Defines performance scenarios that a page can be in.
//
// Each enum is a list of mutually-exclusive scenarios. The complete scenario
// state is a tuple of all scenarios that are detected, at most one from each
// enum.
//
// The browser process detects which scenarios apply and shares that state with
// child processes over shared memory. Each process can view a global scenario
// list over the entire browser (eg. some page is loading) or a scenario list
// targeted only to that process (eg. a page hosted in this process is loading).
// Scenarios indicating a page is loading.
enum class LoadingScenario {
// No pages covered by the scenario are loading.
kNoPageLoading = 0,
// The focused page is loading. Implies the page is also visible.
kFocusedPageLoading,
// The focused page (if any) is not loading, but a visible page is loading.
kVisiblePageLoading,
// No visible pages are loading, but a non-visible page is.
kBackgroundPageLoading,
};
using LoadingScenarios =
base::EnumSet<LoadingScenario,
/*Min=*/LoadingScenario::kNoPageLoading,
/*Max=*/LoadingScenario::kBackgroundPageLoading>;
// Scenarios indicating user input.
enum class InputScenario {
// No input was detected.
kNoInput = 0,
// The user is typing in the focused page.
kTyping,
};
using InputScenarios = base::EnumSet<InputScenario,
/*Min=*/InputScenario::kNoInput,
/*Max=*/InputScenario::kTyping>;
// The scope that a scenario covers.
enum class ScenarioScope {
// The scenario covers only pages hosted in the current process.
kCurrentProcess,
// The scenario covers the whole browser.
kGlobal,
};
using ScenarioScopes = base::EnumSet<ScenarioScope,
/*Min=*/ScenarioScope::kCurrentProcess,
/*Max=*/ScenarioScope::kGlobal>;
// Different subsets of scenarios that can be checked with the ScenariosMatch()
// function or a MatchingScenarioObserver.
//
// A given ScenarioScope `scope` matches a ScenarioPattern if all of:
//
// * GetLoadingScenario(scope) returns a value in the `loading` set, or the set
// is empty.
// * GetInputScenarios(scope) returns a value in the `input` set, or the set is
// empty.
struct COMPONENT_EXPORT(SCENARIO_API) ScenarioPattern {
// Set of LoadingScenarios that match the pattern. If this is empty, any
// LoadingScenario matches.
LoadingScenarios loading;
// Set of InputScenarios that match the pattern. If this is empty, any
// InputScenario matches.
InputScenarios input;
};
// A ScenarioPattern for a scope that's considered "idle": only background pages
// are loading and there is no input. This is a good definition of "idle" for
// most purposes, but some features that are particularly sensitive to different
// scenarios may want to define a different ScenarioPattern.
inline constexpr ScenarioPattern kDefaultIdleScenarios{
.loading = {LoadingScenario::kNoPageLoading,
LoadingScenario::kBackgroundPageLoading},
.input = {InputScenario::kNoInput},
};
// The full scenario state to copy over shared memory.
// TODO(crbug.com/365586676): Move this to a separate header since it's part of
// the plumbing, not the general API.
#pragma clang diagnostic push
#pragma clang diagnostic error "-Wpadded"
struct COMPONENT_EXPORT(SCENARIO_API) ScenarioState {
base::subtle::SharedAtomic<LoadingScenario> loading;
base::subtle::SharedAtomic<InputScenario> input;
};
#pragma clang diagnostic pop
// Pointers to the mapped shared memory are held in thread-safe scoped_refptr's.
// The memory will be unmapped when the final reference is dropped. Functions
// that copy values out of the shared memory must hold a reference to it so that
// it's not unmapped while reading.
using RefCountedScenarioMapping = base::RefCountedData<
base::StructuredSharedMemory<ScenarioState>::ReadOnlyMapping>;
// A wrapper around a std::atomic<T> that's stored in shared memory. The wrapper
// prevents the shared memory from being unmapped while a caller has a reference
// to the atomic. Dereference the SharedAtomicRef to read from it as a
// std::atomic. See the comments above GetLoadingScenario() for usage notes.
template <typename T>
class SharedAtomicRef {
public:
SharedAtomicRef(scoped_refptr<RefCountedScenarioMapping> mapping,
const std::atomic<T>& wrapped_atomic)
: mapping_(std::move(mapping)), wrapped_atomic_(wrapped_atomic) {}
~SharedAtomicRef() = default;
// Move-only.
SharedAtomicRef(const SharedAtomicRef&) = delete;
SharedAtomicRef& operator=(const SharedAtomicRef&) = delete;
SharedAtomicRef(SharedAtomicRef&&) = default;
SharedAtomicRef& operator=(SharedAtomicRef&&) = default;
// Smart-pointer-like interface:
// Returns a pointer to the wrapped atomic.
const std::atomic<T>* get() const { return &wrapped_atomic_; }
// Returns a reference to the wrapped atomic.
const std::atomic<T>& operator*() const { return wrapped_atomic_; }
// Returns a pointer to the wrapped atomic for method invocation.
const std::atomic<T>* operator->() const { return &wrapped_atomic_; }
private:
const scoped_refptr<RefCountedScenarioMapping> mapping_;
// A reference into `mapping_`, not PartitionAlloc memory.
RAW_PTR_EXCLUSION const std::atomic<T>& wrapped_atomic_;
};
// A scoped object that maps shared memory for the scenario state into the
// current process as long as it exists.
// TODO(crbug.com/365586676): Move this to a separate header since it's part of
// the plumbing, not the general API.
class COMPONENT_EXPORT(SCENARIO_API) ScopedReadOnlyScenarioMemory {
public:
// Maps `region` into the current process, as a read-only view of the memory
// holding the scenario state for `scope`.
ScopedReadOnlyScenarioMemory(ScenarioScope scope,
base::ReadOnlySharedMemoryRegion region);
~ScopedReadOnlyScenarioMemory();
ScopedReadOnlyScenarioMemory(const ScopedReadOnlyScenarioMemory&) = delete;
ScopedReadOnlyScenarioMemory& operator=(const ScopedReadOnlyScenarioMemory&) =
delete;
// Returns a pointer to the mapping registered for `scope`, if any.
static scoped_refptr<RefCountedScenarioMapping> GetMappingForTesting(
ScenarioScope scope);
private:
ScenarioScope scope_;
};
// Functions to query performance scenarios.
//
// Since the scenarios can be modified at any time from another process, they're
// accessed through SharedAtomicRef. Get a snapshot of the scenario with
// std::atomic::load(). std::memory_order_relaxed is usually sufficient since no
// other memory depends on the scenario value.
//
// Usage:
//
// // Test whether any foreground page is loading.
// LoadingScenario scenario = GetLoadingScenario(ScenarioScope::kGlobal)
// ->load(std::memory_order_relaxed);
// if (scenario == LoadingScenario::kFocusedPageLoading ||
// scenario == LoadingScenario::kVisiblePageLoading) {
// ... delay less-important work until scenario changes ...
// }
//
// // Inverse of the above test: true if NO foreground page is loading.
// if (CurrentScenariosMatch(ScenarioScope::kGlobal,
// ScenarioPattern{.loading = {
// LoadingScenario::kNoPageLoading,
// LoadingScenario::kBackgroundPageLoading,
// }) {
// ... good time to do less-important work ...
// }
//
// // Test whether the current process is in the critical path for user input.
// if (GetInputScenario(ScenarioScope::kCurrentProcess)->load(
// std::memory_order_relaxed) != InputScenario::kNoInput) {
// ... current process should prioritize input responsiveness ...
// }
//
// // Equivalently:
// if (!CurrentScenariosMatch(ScenarioScope::kCurrentProcess,
// ScenarioPattern{
// .input = {InputScenario::kNoInput}
// }) {
// ... current process should prioritize input responsiveness ...
// }
//
// // Test whether the browser overall is idle by the most common definition.
// if (CurrentScenariosMatch(ScenarioScope::kGlobal, kDefaultIdleScenarios)) {
// ... good time to do maintenance tasks ...
// }
// Returns a reference to the loading scenario for `scope`.
COMPONENT_EXPORT(SCENARIO_API)
SharedAtomicRef<LoadingScenario> GetLoadingScenario(ScenarioScope scope);
// Returns a reference to the input scenario for `scope`.
COMPONENT_EXPORT(SCENARIO_API)
SharedAtomicRef<InputScenario> GetInputScenario(ScenarioScope scope);
// Returns true if `scope` currently matches `pattern`.
COMPONENT_EXPORT(SCENARIO_API)
bool CurrentScenariosMatch(ScenarioScope scope, ScenarioPattern pattern);
// Returns true if the given scenarios match `pattern`.
COMPONENT_EXPORT(SCENARIO_API)
bool ScenariosMatch(LoadingScenario loading_scenario,
InputScenario input_scenario,
ScenarioPattern pattern);
} // namespace performance_scenarios
#endif // COMPONENTS_PERFORMANCE_MANAGER_SCENARIO_API_PERFORMANCE_SCENARIOS_H_
|