blob: 7950758b8fd8dceba4ccfdf1869efffca2fe7c61 [file] [log] [blame]
// 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/scoped_refptr.h"
#include "components/performance_manager/scenario_api/performance_scenario_memory_forward.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).
//
// Additional functions to let the browser process query the performance
// scenarios for a child process are in
// components/performance_manager/public/scenarios/process_performance_scenarios.h.
// Scenarios indicating a page is loading, ordered from least-specific to
// most-specific.
enum class LoadingScenario {
// No pages covered by the scenario are loading.
kNoPageLoading = 0,
// No visible pages are loading, but a non-visible page is.
kBackgroundPageLoading,
// The focused page (if any) is not loading, but a visible page is.
kVisiblePageLoading,
// The focused page is loading. Implies the page is also visible.
kFocusedPageLoading,
kMax = kFocusedPageLoading,
};
using LoadingScenarios = base::EnumSet<LoadingScenario,
LoadingScenario::kNoPageLoading,
LoadingScenario::kMax>;
// Scenarios indicating user input, ordered from least-specific to
// most-specific.
enum class InputScenario {
// No input was detected.
kNoInput = 0,
// The user is typing in a focused page. There were no recent taps or scrolls.
kTyping,
// The user tapped a focused page. There may be recent typing, but not
// scrolls.
kTap,
// The user is scrolling in a focused page. There may also be recent typing or
// taps.
kScroll,
kMax = kScroll,
};
using InputScenarios =
base::EnumSet<InputScenario, InputScenario::kNoInput, InputScenario::kMax>;
// 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},
};
// 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_;
};
// 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_