blob: a4020409e7adf3bffd4c8747e7e9828fdd3f9685 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/trees/frame_sequence_tracker.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/traced_value.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/quads/compositor_frame_metadata.h"
#include "ui/gfx/presentation_feedback.h"
namespace cc {
namespace {
enum class ThreadType {
kMain,
kCompositor,
};
constexpr const char* const kBuiltinSequences[] = {
[FrameSequenceTrackerType::kCompositorAnimation] = "CompositorAnimation",
[FrameSequenceTrackerType::kMainThreadAnimation] = "MainThreadAnimation",
[FrameSequenceTrackerType::kPinchZoom] = "PinchZoom",
[FrameSequenceTrackerType::kRAF] = "RAF",
[FrameSequenceTrackerType::kTouchScroll] = "TouchScroll",
[FrameSequenceTrackerType::kWheelScroll] = "WheelScroll",
};
constexpr int kBuiltinSequenceNum = base::size(kBuiltinSequences);
constexpr int kMaximumHistogramIndex = 2 * kBuiltinSequenceNum;
int GetIndexForMetric(ThreadType thread_type, FrameSequenceTrackerType type) {
return thread_type == ThreadType::kMain
? static_cast<int>(type)
: static_cast<int>(type + kBuiltinSequenceNum);
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// FrameSequenceTrackerCollection
FrameSequenceTrackerCollection::FrameSequenceTrackerCollection() {}
FrameSequenceTrackerCollection::~FrameSequenceTrackerCollection() {
removal_trackers_.clear();
DCHECK(frame_trackers_.empty());
}
std::unique_ptr<FrameSequenceTracker>
FrameSequenceTrackerCollection::CreateTracker(FrameSequenceTrackerType type) {
// The collection always outlives the trackers. So using Unretained() here is
// safe.
auto tracker = base::WrapUnique(new FrameSequenceTracker(
type, base::BindOnce(&FrameSequenceTrackerCollection::RemoveFrameTracker,
base::Unretained(this))));
AddFrameTracker(tracker.get());
return tracker;
}
void FrameSequenceTrackerCollection::ScheduleRemoval(
std::unique_ptr<FrameSequenceTracker> tracker) {
if (!tracker)
return;
tracker->ScheduleTerminate();
removal_trackers_.push_back(std::move(tracker));
}
void FrameSequenceTrackerCollection::ClearAll() {
removal_trackers_.clear();
DCHECK(frame_trackers_.empty());
}
void FrameSequenceTrackerCollection::NotifyBeginImplFrame(
const viz::BeginFrameArgs& args) {
for (auto* tracker : frame_trackers_) {
tracker->ReportBeginImplFrame(args);
}
}
void FrameSequenceTrackerCollection::NotifyBeginMainFrame(
const viz::BeginFrameArgs& args) {
for (auto* tracker : frame_trackers_) {
tracker->ReportBeginMainFrame(args);
}
}
void FrameSequenceTrackerCollection::NotifyImplFrameCausedNoDamage(
const viz::BeginFrameAck& ack) {
for (auto* tracker : frame_trackers_) {
tracker->ReportImplFrameCausedNoDamage(ack);
}
}
void FrameSequenceTrackerCollection::NotifyMainFrameCausedNoDamage(
const viz::BeginFrameArgs& args) {
for (auto* tracker : frame_trackers_) {
tracker->ReportMainFrameCausedNoDamage(args);
}
}
void FrameSequenceTrackerCollection::NotifyPauseFrameProduction() {
for (auto* tracker : frame_trackers_)
tracker->PauseFrameProduction();
}
void FrameSequenceTrackerCollection::NotifySubmitFrame(
uint32_t frame_token,
const viz::BeginFrameAck& ack,
const viz::BeginFrameArgs& origin_args) {
for (auto* tracker : frame_trackers_) {
tracker->ReportSubmitFrame(frame_token, ack, origin_args);
}
}
void FrameSequenceTrackerCollection::NotifyFramePresented(
uint32_t frame_token,
const gfx::PresentationFeedback& feedback) {
for (auto* tracker : frame_trackers_)
tracker->ReportFramePresented(frame_token, feedback);
// Destroy the trackers that are ready to be terminated.
base::EraseIf(
removal_trackers_,
[](const std::unique_ptr<FrameSequenceTracker>& tracker) {
return tracker->termination_status() ==
FrameSequenceTracker::TerminationStatus::kReadyForTermination;
});
}
void FrameSequenceTrackerCollection::AddFrameTracker(
FrameSequenceTracker* tracker) {
frame_trackers_.push_back(tracker);
}
void FrameSequenceTrackerCollection::RemoveFrameTracker(
FrameSequenceTracker* tracker) {
base::Erase(frame_trackers_, tracker);
}
////////////////////////////////////////////////////////////////////////////////
// FrameSequenceTracker
FrameSequenceTracker::FrameSequenceTracker(
FrameSequenceTrackerType type,
base::OnceCallback<void(FrameSequenceTracker*)> destroy_callback)
: type_(type), destroy_callback_(std::move(destroy_callback)) {
DCHECK_LT(type_, FrameSequenceTrackerType::kMaxType);
TRACE_EVENT_ASYNC_BEGIN1("cc,benchmark", "FrameSequenceTracker", this, "name",
TRACE_STR_COPY(kBuiltinSequences[type_]));
}
FrameSequenceTracker::~FrameSequenceTracker() {
DCHECK_LE(impl_throughput_.frames_produced, impl_throughput_.frames_expected);
DCHECK_LE(main_throughput_.frames_produced, main_throughput_.frames_expected);
DCHECK_LE(main_throughput_.frames_produced, impl_throughput_.frames_produced);
TRACE_EVENT_ASYNC_END1(
"cc,benchmark", "FrameSequenceTracker", this, "args",
ThroughputData::ToTracedValue(impl_throughput_, main_throughput_));
ThroughputData::ReportHistogram(
type_, "CompositorThread",
GetIndexForMetric(ThreadType::kCompositor, type_), impl_throughput_);
ThroughputData::ReportHistogram(type_, "MainThread",
GetIndexForMetric(ThreadType::kMain, type_),
main_throughput_);
std::move(destroy_callback_).Run(this);
}
void FrameSequenceTracker::ReportBeginImplFrame(
const viz::BeginFrameArgs& args) {
if (termination_status_ != TerminationStatus::kActive)
return;
if (ShouldIgnoreBeginFrameSource(args.source_id))
return;
UpdateTrackedFrameData(&begin_impl_frame_data_, args.source_id,
args.sequence_number);
impl_throughput_.frames_expected +=
begin_impl_frame_data_.previous_sequence_delta;
}
void FrameSequenceTracker::ReportBeginMainFrame(
const viz::BeginFrameArgs& args) {
if (termination_status_ != TerminationStatus::kActive)
return;
if (ShouldIgnoreBeginFrameSource(args.source_id))
return;
UpdateTrackedFrameData(&begin_main_frame_data_, args.source_id,
args.sequence_number);
if (first_received_main_sequence_ == 0)
first_received_main_sequence_ = args.sequence_number;
main_throughput_.frames_expected +=
begin_main_frame_data_.previous_sequence_delta;
}
void FrameSequenceTracker::ReportSubmitFrame(
uint32_t frame_token,
const viz::BeginFrameAck& ack,
const viz::BeginFrameArgs& origin_args) {
if (termination_status_ != TerminationStatus::kActive)
return;
if (ShouldIgnoreBeginFrameSource(ack.source_id))
return;
if (begin_impl_frame_data_.previous_sequence == 0 ||
ack.sequence_number < begin_impl_frame_data_.previous_sequence) {
return;
}
if (first_submitted_frame_ == 0)
first_submitted_frame_ = frame_token;
last_submitted_frame_ = frame_token;
if (!ShouldIgnoreBeginFrameSource(origin_args.source_id) &&
first_received_main_sequence_ &&
origin_args.sequence_number >= first_received_main_sequence_) {
if (last_submitted_main_sequence_ == 0 ||
origin_args.sequence_number > last_submitted_main_sequence_) {
last_submitted_main_sequence_ = origin_args.sequence_number;
main_frames_.push_back(frame_token);
DCHECK_GE(main_throughput_.frames_expected, main_frames_.size());
}
}
}
void FrameSequenceTracker::ReportFramePresented(
uint32_t frame_token,
const gfx::PresentationFeedback& feedback) {
const bool frame_token_acks_last_frame =
frame_token == last_submitted_frame_ ||
viz::FrameTokenGT(frame_token, last_submitted_frame_);
// Update termination status if this is scheduled for termination, and it is
// not waiting for any frames, or it has received the presentation-feedback
// for the latest frame it is tracking.
if (termination_status_ == TerminationStatus::kScheduledForTermination &&
(last_submitted_frame_ == 0 || frame_token_acks_last_frame)) {
termination_status_ = TerminationStatus::kReadyForTermination;
}
if (first_submitted_frame_ == 0 ||
viz::FrameTokenGT(first_submitted_frame_, frame_token)) {
// We are getting presentation feedback for frames that were submitted
// before this sequence started. So ignore these.
return;
}
const bool was_presented = !feedback.timestamp.is_null();
if (was_presented && last_submitted_frame_) {
DCHECK_LT(impl_throughput_.frames_produced,
impl_throughput_.frames_expected);
++impl_throughput_.frames_produced;
if (frame_token_acks_last_frame)
last_submitted_frame_ = 0;
}
while (!main_frames_.empty() &&
!viz::FrameTokenGT(main_frames_.front(), frame_token)) {
if (was_presented && main_frames_.front() == frame_token) {
DCHECK_LT(main_throughput_.frames_produced,
main_throughput_.frames_expected);
++main_throughput_.frames_produced;
}
main_frames_.pop_front();
}
}
void FrameSequenceTracker::ReportImplFrameCausedNoDamage(
const viz::BeginFrameAck& ack) {
if (termination_status_ != TerminationStatus::kActive)
return;
if (ShouldIgnoreBeginFrameSource(ack.source_id))
return;
// It is possible that this is called before a begin-impl-frame has been
// dispatched for this frame-sequence. In such cases, ignore this call.
if (begin_impl_frame_data_.previous_sequence == 0 ||
ack.sequence_number < begin_impl_frame_data_.previous_sequence) {
return;
}
DCHECK_GT(impl_throughput_.frames_expected, 0u);
DCHECK_GT(impl_throughput_.frames_expected, impl_throughput_.frames_produced);
--impl_throughput_.frames_expected;
if (begin_impl_frame_data_.previous_sequence == ack.sequence_number)
begin_impl_frame_data_.previous_sequence = 0;
}
void FrameSequenceTracker::ReportMainFrameCausedNoDamage(
const viz::BeginFrameArgs& args) {
if (termination_status_ != TerminationStatus::kActive)
return;
if (ShouldIgnoreBeginFrameSource(args.source_id))
return;
// It is possible that this is called before a begin-main-frame has been
// dispatched for this frame-sequence. In such cases, ignore this call.
if (begin_main_frame_data_.previous_sequence == 0 ||
args.sequence_number < begin_main_frame_data_.previous_sequence) {
return;
}
DCHECK_GT(main_throughput_.frames_expected, 0u);
DCHECK_GT(main_throughput_.frames_expected, main_throughput_.frames_produced);
--main_throughput_.frames_expected;
DCHECK_GE(main_throughput_.frames_expected, main_frames_.size());
if (begin_main_frame_data_.previous_sequence == args.sequence_number)
begin_main_frame_data_.previous_sequence = 0;
}
void FrameSequenceTracker::PauseFrameProduction() {
// Reset the states, so that the tracker ignores the vsyncs until the next
// received begin-frame.
begin_impl_frame_data_ = {0, 0, 0};
begin_main_frame_data_ = {0, 0, 0};
}
void FrameSequenceTracker::UpdateTrackedFrameData(TrackedFrameData* frame_data,
uint64_t source_id,
uint64_t sequence_number) {
if (frame_data->previous_sequence &&
frame_data->previous_source == source_id) {
uint8_t current_latency = sequence_number - frame_data->previous_sequence;
frame_data->previous_sequence_delta = current_latency;
} else {
frame_data->previous_sequence_delta = 1;
}
frame_data->previous_source = source_id;
frame_data->previous_sequence = sequence_number;
}
bool FrameSequenceTracker::ShouldIgnoreBeginFrameSource(
uint64_t source_id) const {
if (begin_impl_frame_data_.previous_source == 0)
return false;
return source_id != begin_impl_frame_data_.previous_source;
}
std::unique_ptr<base::trace_event::TracedValue>
FrameSequenceTracker::ThroughputData::ToTracedValue(
const ThroughputData& impl,
const ThroughputData& main) {
auto dict = std::make_unique<base::trace_event::TracedValue>();
dict->SetInteger("impl-frames-produced", impl.frames_produced);
dict->SetInteger("impl-frames-expected", impl.frames_expected);
dict->SetInteger("main-frames-produced", main.frames_produced);
dict->SetInteger("main-frames-expected", main.frames_expected);
return dict;
}
void FrameSequenceTracker::ThroughputData::ReportHistogram(
FrameSequenceTrackerType sequence_type,
const char* thread_name,
int metric_index,
const ThroughputData& data) {
DCHECK_LT(sequence_type, FrameSequenceTrackerType::kMaxType);
UMA_HISTOGRAM_COUNTS_1000("Graphics.Smoothness.FrameSequenceLength",
data.frames_expected);
// Avoid reporting any throughput metric for sequences that had a small amount
// of frames.
constexpr int kMinFramesForThroughputMetric = 4;
if (data.frames_expected < kMinFramesForThroughputMetric)
return;
const std::string name =
base::StrCat({"Graphics.Smoothness.Throughput.", thread_name, ".",
kBuiltinSequences[sequence_type]});
const int percent =
static_cast<int>(100 * data.frames_produced / data.frames_expected);
STATIC_HISTOGRAM_POINTER_GROUP(
name, metric_index, kMaximumHistogramIndex, Add(percent),
base::LinearHistogram::FactoryGet(
name, 1, 100, 101, base::HistogramBase::kUmaTargetedHistogramFlag));
}
} // namespace cc