// Copyright 2023 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/exo/frame_timing_history.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/not_fatal_until.h"
namespace exo {
namespace {
constexpr size_t kRollingHistorySize = 60u;
constexpr double kFrameTransferDurationEstimationPercentile = 90.0;
// Reports metrics when the number of data points reaches this threshold.
constexpr int32_t kReportMetricsThreshold = 100;
} // namespace
FrameTimingHistory::FrameTimingHistory()
: frame_transfer_duration_history_(kRollingHistorySize) {}
FrameTimingHistory::~FrameTimingHistory() = default;
base::TimeDelta FrameTimingHistory::GetFrameTransferDurationEstimate() const {
return frame_transfer_duration_history_.Percentile(
kFrameTransferDurationEstimationPercentile);
}
void FrameTimingHistory::BeginFrameArrived(const viz::BeginFrameId& id) {
begin_frame_arrival_time_[id] = base::TimeTicks::Now();
}
void FrameTimingHistory::FrameArrived() {
last_frame_arrival_time_ = base::TimeTicks::Now();
}
void FrameTimingHistory::FrameSubmitted(const viz::BeginFrameId& begin_frame_id,
uint32_t frame_token) {
DCHECK(pending_submitted_time_.find(frame_token) ==
pending_submitted_time_.end());
base::TimeTicks submitted_time = base::TimeTicks::Now();
pending_submitted_time_[frame_token] = submitted_time;
// At destruction time, LayerTreeFrameSinkHolder submits an empty frame which
// is not received from the client, skip reporting value for that.
if (!last_frame_arrival_time_.is_null()) {
UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"Graphics.Exo.Smoothness.FrameArrivalToSubmission",
submitted_time - last_frame_arrival_time_, base::Microseconds(1),
base::Milliseconds(50), 50);
}
auto iter = begin_frame_arrival_time_.find(begin_frame_id);
// This could be an unsolicited frame submission. In that case
// `begin_frame_id` won't be found in the map.
if (iter != begin_frame_arrival_time_.end()) {
UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"Graphics.Exo.Smoothness.BeginFrameArrivalToSubmission",
submitted_time - iter->second, base::Microseconds(1),
base::Milliseconds(50), 50);
}
RecordFrameResponseToRemote(begin_frame_id, /*did_not_produce=*/false,
submitted_time);
RecordFrameHandled(/*discarded=*/false);
consecutive_did_not_produce_count_ = 0;
}
void FrameTimingHistory::FrameDidNotProduce(const viz::BeginFrameId& id) {
RecordFrameResponseToRemote(id, /*did_not_produce=*/true,
base::TimeTicks::Now());
consecutive_did_not_produce_count_++;
}
void FrameTimingHistory::FrameReceivedAtRemoteSide(
uint32_t frame_token,
base::TimeTicks received_time) {
auto iter = pending_submitted_time_.find(frame_token);
CHECK(iter != pending_submitted_time_.end(), base::NotFatalUntil::M130)
<< "Frame submitted time information is missing. Frame Token: "
<< frame_token;
DCHECK_GE(received_time, iter->second);
frame_transfer_duration_history_.InsertSample(received_time - iter->second);
pending_submitted_time_.erase(iter);
// FrameSubmitted() / FrameReceivedAtRemoteSide() are supposed to match, so
// that the map won't grow indefinitely.
DCHECK_LE(pending_submitted_time_.size(), 60u);
}
void FrameTimingHistory::FrameDiscarded() {
RecordFrameHandled(/*discarded=*/true);
}
void FrameTimingHistory::MayRecordDidNotProduceToFrameArrvial(bool valid) {
if (last_did_not_produce_time_.is_null()) {
return;
}
base::TimeDelta duration =
valid ? (base::TimeTicks::Now() - last_did_not_produce_time_)
: base::TimeDelta();
// Measures the time duration between Exo sending a DidNotProduceFrame
// response and the next frame arrival, if the next frame arrives before a new
// BeginFrame. Reported for clients with high-resolution clocks.
// This metric is used to measure whether the deadline Exo uses to wait for
// frames is reasonable. Please note that a value is reported for each
// DidNotProduceFrame. If (1) DidNotProduceFrame is issued when there are
// already queued BeginFrames; or (2) a new BeginFrame arrives before the next
// frame; or (3) BeginFrames are paused, then the value reported is 0.
LOCAL_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"Graphics.Exo.Smoothness.DidNotProduceToFrameArrival", duration,
base::Microseconds(1), base::Milliseconds(50), 50);
last_did_not_produce_time_ = {};
}
void FrameTimingHistory::RecordFrameResponseToRemote(
const viz::BeginFrameId& begin_frame_id,
bool did_not_produce,
base::TimeTicks response_time) {
begin_frame_arrival_time_.erase(begin_frame_id);
// All BeginFrames are supposed to be matched with either a frame
// submission or a DidNotProduce response, except at destruction time. So the
// map shouldn't grow indefinitely.
DCHECK_LE(begin_frame_arrival_time_.size(), 60u);
last_did_not_produce_time_ =
did_not_produce ? response_time : base::TimeTicks();
frame_response_count_++;
if (did_not_produce) {
frame_response_did_not_produce_++;
}
if (frame_response_count_ >= kReportMetricsThreshold) {
// Tracks the percent of BeginFrames that Exo receives and responds with
// DidNotProduceFrame.
// A new value is reported for every 100 BeginFrames that a shell surface
// receives. Note that this metric is reported only when there are
// sufficient number of BeginFrames (>=100).
LOCAL_HISTOGRAM_PERCENTAGE(
"Graphics.Exo.Smoothness.PercentDidNotProduceFrame",
frame_response_did_not_produce_ * 100 / frame_response_count_);
frame_response_count_ = 0;
frame_response_did_not_produce_ = 0;
}
}
void FrameTimingHistory::RecordFrameHandled(bool discarded) {
last_frame_arrival_time_ = base::TimeTicks();
frame_handling_count_++;
if (discarded) {
frame_handling_discarded_++;
}
if (frame_handling_count_ >= kReportMetricsThreshold) {
// Tracks the percent of compositor frames that are submitted from Exo
// clients and directly discarded without being sent to the GPU process. A
// new value is reported for every 100 frames that a shell surface submits.
// Note that this metric is reported only when there are sufficient number
// of frames (>=100).
LOCAL_HISTOGRAM_PERCENTAGE(
"Graphics.Exo.Smoothness.PercentFrameDiscarded",
frame_handling_discarded_ * 100 / frame_handling_count_);
frame_handling_count_ = 0;
frame_handling_discarded_ = 0;
}
}
} // namespace exo