blob: 271b913c54be1432c2da48506518cc91887780bd [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.
#include "components/performance_manager/scenarios/browser_performance_scenarios.h"
#include <atomic>
#include <memory>
#include <utility>
#include "base/check_op.h"
#include "base/containers/span.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/structured_shared_memory.h"
#include "base/memory/weak_ptr.h"
#include "base/no_destructor.h"
#include "base/trace_event/typed_macros.h"
#include "base/types/pass_key.h"
#include "components/performance_manager/public/graph/graph.h"
#include "components/performance_manager/public/graph/process_node.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/scenario_api/performance_scenario_memory.h"
#include "components/performance_manager/scenario_api/performance_scenario_observer.h"
#include "components/performance_manager/scenario_api/performance_scenarios.h"
#include "components/performance_manager/scenarios/performance_scenario_data.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "third_party/perfetto/include/perfetto/tracing/string_helpers.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
namespace performance_manager {
using performance_scenarios::MatchingScenarioObserver;
using performance_scenarios::PerformanceScenarioObserver;
using performance_scenarios::PerformanceScenarioObserverList;
using performance_scenarios::ScenarioScope;
namespace {
void MaybeEmitNestingChangeEvent(
const perfetto::NamedTrack* track,
size_t old_nesting_level,
size_t new_nesting_level,
base::span<const perfetto::StaticString> event_names) {
// Close trace events for each removed nesting level.
for (size_t i = old_nesting_level; track && i > new_nesting_level; --i) {
TRACE_EVENT_END("performance_scenarios", *track);
}
// Open trace events for each added nesting level.
for (size_t i = old_nesting_level; track && i < new_nesting_level; ++i) {
TRACE_EVENT_BEGIN("performance_scenarios", event_names.at(i), *track);
}
}
// Generic methods that change according to the Scenario type.
template <typename Scenario>
struct ScenarioTraits {
explicit ScenarioTraits(PerformanceScenarioData* state_ptr);
// Returns a reference to the Scenario slot in shared memory.
std::atomic<Scenario>& ScenarioRef();
// Records trace events for a switch from `old_scenario` to `new_scenario` if
// a tracing track is registered.
void MaybeRecordTraceEvent(Scenario old_scenario,
Scenario new_scenario) const;
// Notifies a ProcessNode's PerformanceScenarioObserver list of a switch from
// `old_scenario` to `new_scenario`.
void NotifyProcessObservers(Scenario old_scenario,
Scenario new_scenario) const;
};
template <>
struct ScenarioTraits<LoadingScenario> {
explicit ScenarioTraits(PerformanceScenarioData* state_ptr)
: state_ptr(state_ptr) {}
std::atomic<LoadingScenario>& ScenarioRef() {
return state_ptr->shared_state().WritableRef().loading;
}
void MaybeRecordTraceEvent(LoadingScenario old_scenario,
LoadingScenario new_scenario) const {
MaybeEmitNestingChangeEvent(
state_ptr->loading_tracing_track(), static_cast<size_t>(old_scenario),
static_cast<size_t>(new_scenario),
{"AnyPageLoading", "VisiblePageLoading", "FocusedPageLoading"});
}
void NotifyProcessObservers(LoadingScenario old_scenario,
LoadingScenario new_scenario) const {
state_ptr->observers().Notify(
&PerformanceScenarioObserver::OnLoadingScenarioChanged,
ScenarioScope::kCurrentProcess, old_scenario, new_scenario);
InputScenario input_scenario =
state_ptr->shared_state().ReadOnlyRef().input.load(
std::memory_order_relaxed);
state_ptr->matching_observers().Notify(
&MatchingScenarioObserver::NotifyIfScenarioMatchChanged,
ScenarioScope::kCurrentProcess, new_scenario, input_scenario);
}
raw_ptr<PerformanceScenarioData> state_ptr;
};
template <>
struct ScenarioTraits<InputScenario> {
explicit ScenarioTraits(PerformanceScenarioData* state_ptr)
: state_ptr(state_ptr) {}
std::atomic<InputScenario>& ScenarioRef() {
return state_ptr->shared_state().WritableRef().input;
}
void MaybeRecordTraceEvent(InputScenario old_scenario,
InputScenario new_scenario) const {
MaybeEmitNestingChangeEvent(state_ptr->input_tracing_track(),
static_cast<size_t>(old_scenario),
static_cast<size_t>(new_scenario),
{"TypingTapOrScroll", "TapOrScroll", "Scroll"});
}
void NotifyProcessObservers(InputScenario old_scenario,
InputScenario new_scenario) const {
state_ptr->observers().Notify(
&PerformanceScenarioObserver::OnInputScenarioChanged,
ScenarioScope::kCurrentProcess, old_scenario, new_scenario);
LoadingScenario loading_scenario =
state_ptr->shared_state().ReadOnlyRef().loading.load(
std::memory_order_relaxed);
state_ptr->matching_observers().Notify(
&MatchingScenarioObserver::NotifyIfScenarioMatchChanged,
ScenarioScope::kCurrentProcess, loading_scenario, new_scenario);
}
raw_ptr<PerformanceScenarioData> state_ptr;
};
// Holds the browser's global scenario state handle.
std::unique_ptr<PerformanceScenarioData>& GlobalSharedStatePtr() {
static base::NoDestructor<std::unique_ptr<PerformanceScenarioData>> state_ptr;
return *state_ptr;
}
// Returns a pointer to the shared memory region for communicating private state
// for `process_node`. Creates a region if none exists yet, returning nullptr on
// failure. The region's lifetime is tied to `process_node`.
PerformanceScenarioData* GetSharedStateForProcessNode(
const ProcessNode* process_node) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto& data = PerformanceScenarioData::GetOrCreate(process_node);
// Only return the process data if it holds a shared memory region.
return data.HasSharedState() ? &data : nullptr;
}
// Returns a pointer to the global shared memory region that can be read by all
// processes, or nullptr if none exists. GlobalPerformanceScenarioMemory
// manages the lifetime of the region.
PerformanceScenarioData* GetGlobalSharedState() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return GlobalSharedStatePtr().get();
}
// Sets the value for Scenario in the memory region held in `state_ptr` to
// `new_scenario`.
template <typename Scenario>
void SetScenarioValue(ScenarioScope scope,
Scenario new_scenario,
PerformanceScenarioData* state_ptr) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (state_ptr) {
ScenarioTraits<Scenario> traits(state_ptr);
// std::memory_order_relaxed is sufficient since no other memory depends on
// the scenario value.
Scenario old_scenario =
traits.ScenarioRef().exchange(new_scenario, std::memory_order_relaxed);
if (old_scenario != new_scenario) {
traits.MaybeRecordTraceEvent(old_scenario, new_scenario);
switch (scope) {
case ScenarioScope::kCurrentProcess:
// Notify observers for the ProcessNode holding `state_ptr`.
traits.NotifyProcessObservers(old_scenario, new_scenario);
break;
case ScenarioScope::kGlobal:
// Notify all global observers registered in the browser process.
if (auto observers = PerformanceScenarioObserverList::GetForScope(
ScenarioScope::kGlobal)) {
observers->NotifyIfScenarioChanged();
}
break;
}
}
}
}
template <typename Scenario>
void SetScenarioValueForRenderProcessHost(Scenario scenario,
content::RenderProcessHost* host) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::WeakPtr<ProcessNode> process_node =
PerformanceManager::GetProcessNodeForRenderProcessHost(host);
CHECK(process_node);
SetScenarioValue(ScenarioScope::kCurrentProcess, scenario,
GetSharedStateForProcessNode(process_node.get()));
}
} // namespace
void SetGlobalSharedScenarioState(
base::PassKey<ScopedGlobalScenarioMemory>,
std::unique_ptr<PerformanceScenarioData> state) {
// No BrowserThread::UI here because this might be called on the main thread
// before browser threads are set up.
CHECK_NE(state == nullptr, GlobalSharedStatePtr() == nullptr);
GlobalSharedStatePtr() = std::move(state);
}
base::ReadOnlySharedMemoryRegion GetSharedScenarioRegionForProcessNode(
const ProcessNode* process_node) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
PerformanceScenarioData* state_ptr =
GetSharedStateForProcessNode(process_node);
// When this is called, the ProcessTrack should be available.
if (state_ptr) {
state_ptr->EnsureTracingTracks(process_node);
}
return state_ptr ? state_ptr->shared_state().DuplicateReadOnlyRegion()
: base::ReadOnlySharedMemoryRegion();
}
base::ReadOnlySharedMemoryRegion GetGlobalSharedScenarioRegion() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
PerformanceScenarioData* state_ptr = GetGlobalSharedState();
return state_ptr ? state_ptr->shared_state().DuplicateReadOnlyRegion()
: base::ReadOnlySharedMemoryRegion();
}
void SetLoadingScenarioForProcess(LoadingScenario scenario,
content::RenderProcessHost* host) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetScenarioValueForRenderProcessHost(scenario, host);
}
void SetLoadingScenarioForProcessNode(LoadingScenario scenario,
const ProcessNode* process_node) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetScenarioValue(ScenarioScope::kCurrentProcess, scenario,
GetSharedStateForProcessNode(process_node));
}
void SetGlobalLoadingScenario(LoadingScenario scenario) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetScenarioValue(ScenarioScope::kGlobal, scenario, GetGlobalSharedState());
}
void SetInputScenarioForProcess(InputScenario scenario,
content::RenderProcessHost* host) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetScenarioValueForRenderProcessHost(scenario, host);
}
void SetInputScenarioForProcessNode(InputScenario scenario,
const ProcessNode* process_node) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetScenarioValue(ScenarioScope::kCurrentProcess, scenario,
GetSharedStateForProcessNode(process_node));
}
void SetGlobalInputScenario(InputScenario scenario) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetScenarioValue(ScenarioScope::kGlobal, scenario, GetGlobalSharedState());
}
} // namespace performance_manager