blob: f2265296ce37aba318e412ee04a53b2da883f474 [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/jank_metrics.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/trace_event/trace_event.h"
#include "cc/metrics/frame_sequence_tracker.h"
namespace cc {
namespace {
constexpr uint64_t kMaxNoUpdateFrameQueueLength = 100;
constexpr int kBuiltinSequenceNum =
static_cast<int>(FrameSequenceTrackerType::kMaxType) + 1;
constexpr int kMaximumJankHistogramIndex = 2 * kBuiltinSequenceNum;
constexpr int kMaximumStaleHistogramIndex = kBuiltinSequenceNum;
constexpr base::TimeDelta kStaleHistogramMin = base::Microseconds(1);
constexpr base::TimeDelta kStaleHistogramMax = base::Milliseconds(1000);
constexpr int kStaleHistogramBucketCount = 200;
constexpr bool IsValidJankThreadType(
FrameInfo::SmoothEffectDrivingThread type) {
return type == FrameInfo::SmoothEffectDrivingThread::kCompositor ||
type == FrameInfo::SmoothEffectDrivingThread::kMain;
}
const char* GetJankThreadTypeName(FrameInfo::SmoothEffectDrivingThread type) {
DCHECK(IsValidJankThreadType(type));
switch (type) {
case FrameInfo::SmoothEffectDrivingThread::kCompositor:
return "Compositor";
case FrameInfo::SmoothEffectDrivingThread::kMain:
return "Main";
default:
NOTREACHED();
return "";
}
}
int GetIndexForJankMetric(FrameInfo::SmoothEffectDrivingThread thread_type,
FrameSequenceTrackerType type) {
DCHECK(IsValidJankThreadType(thread_type));
if (thread_type == FrameInfo::SmoothEffectDrivingThread::kMain)
return static_cast<int>(type);
DCHECK_EQ(thread_type, FrameInfo::SmoothEffectDrivingThread::kCompositor);
return static_cast<int>(type) + kBuiltinSequenceNum;
}
int GetIndexForStaleMetric(FrameSequenceTrackerType type) {
return static_cast<int>(type);
}
std::string GetJankHistogramName(FrameSequenceTrackerType type,
const char* thread_name) {
return base::StrCat(
{"Graphics.Smoothness.Jank.", thread_name, ".",
FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
}
std::string GetStaleHistogramName(FrameSequenceTrackerType type) {
return base::StrCat(
{"Graphics.Smoothness.Stale.",
FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
}
std::string GetMaxStaleHistogramName(FrameSequenceTrackerType type) {
return base::StrCat(
{"Graphics.Smoothness.MaxStale.",
FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
}
} // namespace
JankMetrics::JankMetrics(FrameSequenceTrackerType tracker_type,
FrameInfo::SmoothEffectDrivingThread effective_thread)
: tracker_type_(tracker_type), effective_thread_(effective_thread) {
DCHECK(IsValidJankThreadType(effective_thread));
}
JankMetrics::~JankMetrics() = default;
void JankMetrics::AddSubmitFrame(uint32_t frame_token,
uint32_t sequence_number) {
// When a frame is submitted, record its |frame_token| and its associated
// |sequence_number|. This pushed item will be removed when this frame is
// presented.
queue_frame_token_and_id_.push({frame_token, sequence_number});
}
void JankMetrics::AddFrameWithNoUpdate(uint32_t sequence_number,
base::TimeDelta frame_interval) {
DCHECK_LE(queue_frame_id_and_interval_.size(), kMaxNoUpdateFrameQueueLength);
// If a frame does not cause an increase in expected frames, it will be
// recorded here and later subtracted from the presentation interval that
// includes this frame.
queue_frame_id_and_interval_.push({sequence_number, frame_interval});
// This prevents the no-update frame queue from growing infinitely on an idle
// page.
if (queue_frame_id_and_interval_.size() > kMaxNoUpdateFrameQueueLength)
queue_frame_id_and_interval_.pop();
}
void JankMetrics::AddPresentedFrame(
uint32_t presented_frame_token,
base::TimeTicks current_presentation_timestamp,
base::TimeDelta frame_interval) {
uint32_t presented_frame_id = 0;
// Find the main_sequence_number of the presented_frame_token
while (!queue_frame_token_and_id_.empty()) {
auto token_and_id = queue_frame_token_and_id_.front();
if (token_and_id.first > presented_frame_token) {
// The submitting of this presented frame was not recorded (e.g. the
// submitting might have occurred before JankMetrics starts recording).
// In that case, do not use this frame presentation for jank detection.
return;
}
queue_frame_token_and_id_.pop();
if (token_and_id.first == presented_frame_token) {
// Found information about the submit of this presented frame;
// retrieve the frame's sequence number.
presented_frame_id = token_and_id.second;
break;
}
}
// If for any reason the sequence number associated with the
// presented_frame_token cannot be identified, then ignore this frame
// presentation.
if (presented_frame_id == 0)
return;
base::TimeDelta no_update_time; // The frame time spanned by the frames that
// have no updates
// If |queue_frame_id_and_interval_| contains an excessive amount of no-update
// frames, it indicates that the current presented frame is most likely the
// first presentation after a long idle period. Such frames are excluded from
// jank/stale calculation because they usually have little impact on
// smoothness perception, and |queue_frame_id_and_interval_| does not hold
// enough data to accurately estimate the effective frame delta.
bool will_ignore_current_frame =
queue_frame_id_and_interval_.size() == kMaxNoUpdateFrameQueueLength;
// Compute the presentation delay contributed by no-update frames that
// began BEFORE (i.e. have smaller sequence number than) the current
// presented frame.
while (!queue_frame_id_and_interval_.empty() &&
queue_frame_id_and_interval_.front().first < presented_frame_id) {
auto id_and_interval = queue_frame_id_and_interval_.front();
if (id_and_interval.first >= last_presentation_frame_id_) {
// Only count no-update frames that began SINCE (i.e. have a greater [or
// equal] sequence number than) the beginning of previous presented frame.
// If, in rare cases, there are still no-update frames that began BEFORE
// the beginning of previous presented frame left in the queue, those
// frames will simply be discarded and not counted into |no_update_time|.
no_update_time += id_and_interval.second;
}
queue_frame_id_and_interval_.pop();
}
// Exclude the presentation delay introduced by no-update frames. If this
// exclusion results in negative frame delta, treat the frame delta as 0.
const base::TimeDelta zero_delta = base::Milliseconds(0);
// Setting the current_frame_delta to zero conveniently excludes the current
// frame to be ignored from jank/stale calculation.
base::TimeDelta current_frame_delta = (will_ignore_current_frame)
? zero_delta
: current_presentation_timestamp -
last_presentation_timestamp_ -
no_update_time;
// Guard against the situation when the physical presentation interval is
// shorter than |no_update_time|. For example, consider two BeginFrames A and
// B separated by 5 vsync cycles of no-updates (i.e. |no_update_time| = 5
// vsync cycles); the Presentation of A occurs 2 vsync cycles after BeginFrame
// A, whereas Presentation B occurs in the same vsync cycle as BeginFrame B.
// In this situation, the physical presentation interval is shorter than 5
// vsync cycles and will result in a negative |current_frame_delta|.
if (current_frame_delta < zero_delta)
current_frame_delta = zero_delta;
// Only start tracking jank if this function has already been
// called at least once (so that |last_presentation_timestamp_|
// and |prev_frame_delta_| have been set).
//
// The presentation interval is typically a multiple of VSync
// intervals (i.e. 16.67ms, 33.33ms, 50ms ... on a 60Hz display)
// with small fluctuations. The 0.5 * |frame_interval| criterion
// is chosen so that the jank detection is robust to those
// fluctuations.
if (!last_presentation_timestamp_.is_null()) {
base::TimeDelta staleness = current_frame_delta - frame_interval;
if (staleness < zero_delta)
staleness = zero_delta;
if (tracker_type_ != FrameSequenceTrackerType::kCustom) {
STATIC_HISTOGRAM_POINTER_GROUP(
GetStaleHistogramName(tracker_type_),
GetIndexForStaleMetric(tracker_type_), kMaximumStaleHistogramIndex,
AddTimeMillisecondsGranularity(staleness),
base::Histogram::FactoryTimeGet(
GetStaleHistogramName(tracker_type_), kStaleHistogramMin,
kStaleHistogramMax, kStaleHistogramBucketCount,
base::HistogramBase::kUmaTargetedHistogramFlag));
if (staleness > max_staleness_)
max_staleness_ = staleness;
}
if (!prev_frame_delta_.is_zero() &&
current_frame_delta > prev_frame_delta_ + 0.5 * frame_interval) {
jank_count_++;
TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1(
"cc,benchmark", "Jank", TRACE_ID_LOCAL(this),
last_presentation_timestamp_, "thread-type",
GetJankThreadTypeName(effective_thread_));
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP1(
"cc,benchmark", "Jank", TRACE_ID_LOCAL(this),
current_presentation_timestamp, "tracker-type",
FrameSequenceTracker::GetFrameSequenceTrackerTypeName(tracker_type_));
}
}
last_presentation_timestamp_ = current_presentation_timestamp;
last_presentation_frame_id_ = presented_frame_id;
prev_frame_delta_ = current_frame_delta;
}
void JankMetrics::ReportJankMetrics(int frames_expected) {
if (tracker_type_ == FrameSequenceTrackerType::kCustom)
return;
int jank_percent = static_cast<int>(100 * jank_count_ / frames_expected);
const char* jank_thread_name = GetJankThreadTypeName(effective_thread_);
STATIC_HISTOGRAM_POINTER_GROUP(
GetJankHistogramName(tracker_type_, jank_thread_name),
GetIndexForJankMetric(effective_thread_, tracker_type_),
kMaximumJankHistogramIndex, Add(jank_percent),
base::LinearHistogram::FactoryGet(
GetJankHistogramName(tracker_type_, jank_thread_name), 1, 100, 101,
base::HistogramBase::kUmaTargetedHistogramFlag));
const bool is_animation =
ShouldReportForAnimation(tracker_type_, effective_thread_);
// Jank reporter's effective thread is guaranteed to be identical to that of
// the owning FrameSequenceMetrics instance.
const bool is_interaction = ShouldReportForInteraction(
tracker_type_, effective_thread_, effective_thread_);
if (is_animation) {
UMA_HISTOGRAM_PERCENTAGE("Graphics.Smoothness.Jank.AllAnimations",
jank_percent);
}
if (is_interaction) {
UMA_HISTOGRAM_PERCENTAGE("Graphics.Smoothness.Jank.AllInteractions",
jank_percent);
}
if (is_animation || is_interaction) {
UMA_HISTOGRAM_PERCENTAGE("Graphics.Smoothness.Jank.AllSequences",
jank_percent);
}
// Report the max staleness metrics
STATIC_HISTOGRAM_POINTER_GROUP(
GetMaxStaleHistogramName(tracker_type_),
GetIndexForStaleMetric(tracker_type_), kMaximumStaleHistogramIndex,
AddTimeMillisecondsGranularity(max_staleness_),
base::Histogram::FactoryTimeGet(
GetMaxStaleHistogramName(tracker_type_), kStaleHistogramMin,
kStaleHistogramMax, kStaleHistogramBucketCount,
base::HistogramBase::kUmaTargetedHistogramFlag));
// Reset counts to avoid duplicated reporting.
Reset();
}
void JankMetrics::Reset() {
jank_count_ = 0;
max_staleness_ = {};
}
void JankMetrics::Merge(std::unique_ptr<JankMetrics> jank_metrics) {
if (jank_metrics) {
jank_count_ += jank_metrics->jank_count_;
max_staleness_ = std::max(max_staleness_, jank_metrics->max_staleness_);
}
}
} // namespace cc