blob: ae92cfb84793ab42c643d0e16818d69bb9d20082 [file] [log] [blame]
// 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 "cc/metrics/frame_sequence_tracker_collection.h"
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/memory/ptr_util.h"
#include "base/time/time.h"
#include "cc/metrics/compositor_frame_reporting_controller.h"
#include "cc/metrics/frame_info.h"
#include "cc/metrics/frame_sequence_metrics.h"
#include "cc/metrics/frame_sequence_tracker.h"
#include "cc/metrics/ukm_dropped_frames_data.h"
namespace cc {
namespace {
using ThreadType = FrameInfo::SmoothEffectDrivingThread;
bool IsScrollType(FrameSequenceTrackerType type) {
return type == FrameSequenceTrackerType::kTouchScroll ||
type == FrameSequenceTrackerType::kWheelScroll ||
type == FrameSequenceTrackerType::kScrollbarScroll;
}
} // namespace
FrameSequenceTrackerCollection::FrameSequenceTrackerCollection(
bool is_single_threaded)
: is_single_threaded_(is_single_threaded) {}
FrameSequenceTrackerCollection::~FrameSequenceTrackerCollection() {
CleanUp();
frame_trackers_.clear();
removal_trackers_.clear();
custom_frame_trackers_.clear();
accumulated_metrics_.clear();
}
void FrameSequenceTrackerCollection::SetScrollingThread(
FrameInfo::SmoothEffectDrivingThread thread) {
auto current_scrolling_thread = scrolling_thread_;
base::TimeTicks set_time = base::TimeTicks::Now();
// Assign the thread.
scrolling_thread_ = thread;
// keep the history for the last 3 seconds.
if (!scroll_thread_history_.empty()) {
auto expired_scrolling_thread =
scroll_thread_history_.lower_bound(set_time - base::Seconds(3));
scroll_thread_history_.erase(scroll_thread_history_.begin(),
expired_scrolling_thread);
}
// Only traces the history if there is a change in scrolling_thread
if (current_scrolling_thread != scrolling_thread_) {
scroll_thread_history_.insert(
std::make_pair(set_time, current_scrolling_thread));
}
}
FrameInfo::SmoothThread FrameSequenceTrackerCollection::GetSmoothThread()
const {
if (main_thread_driving_smoothness_) {
return compositor_thread_driving_smoothness_
? FrameInfo::SmoothThread::kSmoothBoth
: FrameInfo::SmoothThread::kSmoothMain;
}
if (raster_thread_driving_smoothness_) {
return FrameInfo::SmoothThread::kSmoothRaster;
}
return compositor_thread_driving_smoothness_
? FrameInfo::SmoothThread::kSmoothCompositor
: FrameInfo::SmoothThread::kSmoothNone;
}
void FrameSequenceTrackerCollection::UpdateSmoothThreadHistory(
FrameInfo::SmoothEffectDrivingThread thread_type,
int modifier) {
auto current_smooth_thread = GetSmoothThread();
base::TimeTicks set_time = base::TimeTicks::Now();
// Update smooth effect thread tracking count based on the type of
// tracker being created or destroyed.
if (thread_type == FrameInfo::SmoothEffectDrivingThread::kCompositor) {
compositor_thread_driving_smoothness_ += modifier;
} else if (thread_type == FrameInfo::SmoothEffectDrivingThread::kRaster) {
raster_thread_driving_smoothness_ += modifier;
} else {
DCHECK_EQ(thread_type, FrameInfo::SmoothEffectDrivingThread::kMain);
main_thread_driving_smoothness_ += modifier;
}
// Only called if there is a change in smooth_thread_
if (current_smooth_thread != GetSmoothThread()) {
// Keep the history for the last 3 seconds.
if (!smooth_thread_history_.empty()) {
auto expired_smooth_thread =
smooth_thread_history_.lower_bound(set_time - base::Seconds(3));
smooth_thread_history_.erase(smooth_thread_history_.begin(),
expired_smooth_thread);
}
smooth_thread_history_.insert(
std::make_pair(set_time, current_smooth_thread));
}
}
FrameInfo::SmoothThread FrameSequenceTrackerCollection::GetSmoothThreadAtTime(
base::TimeTicks timestamp) const {
auto last_smooth_thread = smooth_thread_history_.lower_bound(timestamp);
if (last_smooth_thread == smooth_thread_history_.end()) {
return GetSmoothThread();
}
return last_smooth_thread->second;
}
FrameInfo::SmoothEffectDrivingThread
FrameSequenceTrackerCollection::GetScrollThreadAtTime(
base::TimeTicks timestamp) const {
auto last_scroll_thread = scroll_thread_history_.lower_bound(timestamp);
if (last_scroll_thread == scroll_thread_history_.end()) {
return scrolling_thread_;
}
return last_scroll_thread->second;
}
FrameInfo::SmoothEffectDrivingThread
FrameSequenceTrackerCollection::GetScrollingThread() const {
return scrolling_thread_;
}
FrameSequenceTracker* FrameSequenceTrackerCollection::StartSequenceInternal(
FrameSequenceTrackerType type,
FrameInfo::SmoothEffectDrivingThread scrolling_thread) {
DCHECK_NE(FrameSequenceTrackerType::kCustom, type);
if (is_single_threaded_)
return nullptr;
auto key = std::make_pair(type, scrolling_thread);
auto [frame_tracker_it, inserted] = frame_trackers_.try_emplace(key, nullptr);
if (!inserted) {
// If the tracker already exists, we should return it.
return frame_tracker_it->second.get();
}
auto& tracker = frame_tracker_it->second;
tracker = base::WrapUnique(new FrameSequenceTracker(type));
active_trackers_.set(static_cast<size_t>(type));
auto* metrics = tracker->metrics();
if (auto it = accumulated_metrics_.find(key);
it != accumulated_metrics_.end()) {
metrics->AdoptTrace(it->second.get());
}
if (IsScrollType(type)) {
DCHECK_NE(scrolling_thread, ThreadType::kUnknown);
metrics->SetScrollingThread(scrolling_thread);
SetScrollingThread(scrolling_thread);
}
if (metrics->GetEffectiveThread() == ThreadType::kCompositor) {
UpdateSmoothThreadHistory(ThreadType::kCompositor, /*modifier=*/1);
} else if (metrics->GetEffectiveThread() == ThreadType::kRaster) {
UpdateSmoothThreadHistory(ThreadType::kRaster, /*modifier=*/1);
} else {
DCHECK_EQ(metrics->GetEffectiveThread(), ThreadType::kMain);
UpdateSmoothThreadHistory(ThreadType::kMain, /*modifier=*/1);
}
return tracker.get();
}
ActiveTrackers FrameSequenceTrackerCollection::GetActiveTrackers() const {
return active_trackers_;
}
FrameSequenceTracker* FrameSequenceTrackerCollection::StartSequence(
FrameSequenceTrackerType type) {
DCHECK(!IsScrollType(type));
return StartSequenceInternal(type, ThreadType::kUnknown);
}
FrameSequenceTracker* FrameSequenceTrackerCollection::StartScrollSequence(
FrameSequenceTrackerType type,
FrameInfo::SmoothEffectDrivingThread scrolling_thread) {
DCHECK(IsScrollType(type));
return StartSequenceInternal(type, scrolling_thread);
}
void FrameSequenceTrackerCollection::CleanUp() {
for (auto& tracker : frame_trackers_)
tracker.second->CleanUp();
for (auto& tracker : custom_frame_trackers_)
tracker.second->CleanUp();
for (auto& tracker : removal_trackers_)
tracker->CleanUp();
for (auto& metric : accumulated_metrics_)
metric.second->ReportLeftoverData();
}
void FrameSequenceTrackerCollection::StopSequence(
FrameSequenceTrackerType type) {
DCHECK_NE(FrameSequenceTrackerType::kCustom, type);
auto key = std::make_pair(type, ThreadType::kUnknown);
if (IsScrollType(type)) {
SetScrollingThread(ThreadType::kUnknown);
key = std::make_pair(type, ThreadType::kCompositor);
if (!frame_trackers_.contains(key))
key = std::make_pair(type, ThreadType::kMain);
if (!frame_trackers_.contains(key)) {
key = std::make_pair(type, ThreadType::kRaster);
}
}
auto tracker_it = frame_trackers_.find(key);
if (tracker_it == frame_trackers_.end()) {
return;
}
auto tracker = std::move(tracker_it->second);
active_trackers_.reset(static_cast<size_t>(tracker->type()));
if (tracker->metrics()->GetEffectiveThread() == ThreadType::kCompositor) {
DCHECK_GT(compositor_thread_driving_smoothness_, 0u);
UpdateSmoothThreadHistory(ThreadType::kCompositor, /*modifier=*/-1);
} else if (tracker->metrics()->GetEffectiveThread() == ThreadType::kRaster) {
DCHECK_GT(raster_thread_driving_smoothness_, 0u);
UpdateSmoothThreadHistory(ThreadType::kRaster, /*modifier=*/-1);
} else {
DCHECK_GT(main_thread_driving_smoothness_, 0u);
UpdateSmoothThreadHistory(ThreadType::kMain, /*modifier=*/-1);
}
frame_trackers_.erase(tracker_it);
tracker->ScheduleTerminate();
removal_trackers_.push_back(std::move(tracker));
DestroyTrackers();
}
void FrameSequenceTrackerCollection::StartCustomSequence(int sequence_id) {
DCHECK(!base::Contains(custom_frame_trackers_, sequence_id));
// base::Unretained() is safe here because |this| owns FrameSequenceTracker
// and FrameSequenceMetrics.
custom_frame_trackers_[sequence_id] =
base::WrapUnique(new FrameSequenceTracker(
sequence_id,
base::BindOnce(
&FrameSequenceTrackerCollection::AddCustomTrackerResult,
base::Unretained(this), sequence_id)));
}
void FrameSequenceTrackerCollection::StopCustomSequence(int sequence_id) {
auto it = custom_frame_trackers_.find(sequence_id);
// This happens when an animation is aborted before starting.
if (it == custom_frame_trackers_.end())
return;
std::unique_ptr<FrameSequenceTracker> tracker = std::move(it->second);
custom_frame_trackers_.erase(it);
tracker->ScheduleTerminate();
removal_trackers_.push_back(std::move(tracker));
DestroyTrackers();
}
void FrameSequenceTrackerCollection::ClearAll() {
frame_trackers_.clear();
custom_frame_trackers_.clear();
removal_trackers_.clear();
}
void FrameSequenceTrackerCollection::NotifyBeginImplFrame(
const viz::BeginFrameArgs& args) {
RecreateTrackers(args);
for (auto& tracker : frame_trackers_)
tracker.second->ReportBeginImplFrame(args);
for (auto& tracker : custom_frame_trackers_)
tracker.second->ReportBeginImplFrame(args);
}
void FrameSequenceTrackerCollection::NotifyPauseFrameProduction() {
for (auto& tracker : frame_trackers_)
tracker.second->PauseFrameProduction();
for (auto& tracker : custom_frame_trackers_)
tracker.second->PauseFrameProduction();
}
void FrameSequenceTrackerCollection::NotifyFrameEnd(
const viz::BeginFrameArgs& args,
const viz::BeginFrameArgs& main_args) {
for (auto& tracker : frame_trackers_)
tracker.second->ReportFrameEnd(args, main_args);
for (auto& tracker : custom_frame_trackers_)
tracker.second->ReportFrameEnd(args, main_args);
// Removal trackers continue to process any frames which they started
// observing.
for (auto& tracker : removal_trackers_)
tracker->ReportFrameEnd(args, main_args);
DestroyTrackers();
}
void FrameSequenceTrackerCollection::DestroyTrackers() {
for (auto& tracker : removal_trackers_) {
if (tracker->termination_status() ==
FrameSequenceTracker::TerminationStatus::kReadyForTermination) {
// The tracker is ready to be terminated.
// For non kCustom typed trackers, take the metrics from the tracker.
// merge with any outstanding metrics from previous trackers of the same
// type. If there are enough frames to report the metrics, then report the
// metrics and destroy it. Otherwise, retain it to be merged with
// follow-up sequences.
// For kCustom typed trackers, |metrics| invokes AddCustomTrackerResult
// on its destruction, which add its data to |custom_tracker_results_|
// to be picked up by caller.
if (tracker->metrics() &&
tracker->type() == FrameSequenceTrackerType::kCustom)
continue;
auto metrics = tracker->TakeMetrics();
auto key = std::make_pair(metrics->type(), metrics->GetEffectiveThread());
auto accumulated_metrics_it = accumulated_metrics_.find(key);
if (accumulated_metrics_it != accumulated_metrics_.end()) {
metrics->Merge(std::move(accumulated_metrics_it->second));
accumulated_metrics_.erase(accumulated_metrics_it);
}
if (metrics->HasEnoughDataForReporting()) {
// This value is guaranteed to be positive by the
// previous HasEnoughDataForReporting check.
int percent_dropped_frames4 = metrics->ReportMetrics();
CHECK_GE(percent_dropped_frames4, 0);
if (ukm_dropped_frames_data_) {
UkmDroppedFramesData dropped_frames_data;
dropped_frames_data.percent_dropped_frames = percent_dropped_frames4;
ukm_dropped_frames_data_->Write(dropped_frames_data);
}
}
if (metrics->HasDataLeftForReporting()) {
accumulated_metrics_.emplace(key, std::move(metrics));
}
}
}
std::erase_if(
removal_trackers_,
[](const std::unique_ptr<FrameSequenceTracker>& tracker) {
return tracker->termination_status() ==
FrameSequenceTracker::TerminationStatus::kReadyForTermination;
});
}
void FrameSequenceTrackerCollection::RecreateTrackers(
const viz::BeginFrameArgs& args) {
std::vector<std::pair<FrameSequenceTrackerType, ThreadType>>
recreate_trackers;
for (const auto& tracker : frame_trackers_) {
if (tracker.second->ShouldReportMetricsNow(args))
recreate_trackers.push_back(tracker.first);
}
for (const auto& key : recreate_trackers) {
DCHECK(frame_trackers_[key]);
auto tracker_type = key.first;
ThreadType thread_type = key.second;
// StopSequence put the tracker in the |removal_trackers_|, which will
// report its throughput data when its frame is presented.
StopSequence(tracker_type);
// The frame sequence is still active, so create a new tracker to keep
// tracking this sequence.
if (thread_type != FrameInfo::SmoothEffectDrivingThread::kUnknown) {
DCHECK(IsScrollType(tracker_type));
StartScrollSequence(tracker_type, thread_type);
} else {
StartSequence(tracker_type);
}
}
}
ActiveFrameSequenceTrackers
FrameSequenceTrackerCollection::FrameSequenceTrackerActiveTypes() const {
ActiveFrameSequenceTrackers encoded_types = 0;
for (const auto& key : frame_trackers_) {
auto thread_type = key.first.first;
encoded_types |= static_cast<ActiveFrameSequenceTrackers>(
1 << static_cast<unsigned>(thread_type));
}
return encoded_types;
}
FrameSequenceTracker*
FrameSequenceTrackerCollection::GetRemovalTrackerForTesting(
FrameSequenceTrackerType type) {
for (const auto& tracker : removal_trackers_)
if (tracker->type() == type)
return tracker.get();
return nullptr;
}
void FrameSequenceTrackerCollection::AddCustomTrackerResult(
int custom_sequence_id,
const FrameSequenceMetrics::CustomReportData& data) {
DCHECK(custom_tracker_results_added_callback_);
CustomTrackerResults results;
results[custom_sequence_id] = data;
custom_tracker_results_added_callback_.Run(results);
}
void FrameSequenceTrackerCollection::AddSortedFrame(
const viz::BeginFrameArgs& args,
const FrameInfo& frame_info) {
for (auto& tracker : frame_trackers_)
tracker.second->AddSortedFrame(args, frame_info);
for (auto& tracker : custom_frame_trackers_)
tracker.second->AddSortedFrame(args, frame_info);
// Sorted frames could arrive after tracker are scheduled for termination.
// Removal trackers continue to report metrics for frames which they started
// observing.
for (auto& tracker : removal_trackers_) {
tracker->AddSortedFrame(args, frame_info);
}
DestroyTrackers();
}
void FrameSequenceTrackerCollection::SetUkmDroppedFramesDestination(
UkmDroppedFramesDataShared* dropped_frames_data) {
ukm_dropped_frames_data_ = dropped_frames_data;
}
} // namespace cc