| // Copyright 2019 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "ui/compositor/presentation_time_recorder.h" | |
| #include <ostream> | |
| #include "base/functional/bind.h" | |
| #include "base/functional/callback.h" | |
| #include "base/logging.h" | |
| #include "base/memory/ptr_util.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/metrics/histogram.h" | |
| #include "base/trace_event/trace_event.h" | |
| #include "third_party/perfetto/include/perfetto/tracing/track.h" | |
| namespace ui { | |
| namespace { | |
| bool report_immediately_for_test = false; | |
| } // namespace | |
| // PresentationTimeRecorderInternal ------------------------------------------- | |
| class PresentationTimeRecorder::PresentationTimeRecorderInternal | |
| : public ui::CompositorObserver { | |
| public: | |
| explicit PresentationTimeRecorderInternal(ui::Compositor* compositor) | |
| : compositor_(compositor) { | |
| compositor_->AddObserver(this); | |
| VLOG(1) << "Start Recording Frame Time"; | |
| } | |
| PresentationTimeRecorderInternal(const PresentationTimeRecorderInternal&) = | |
| delete; | |
| PresentationTimeRecorderInternal& operator=( | |
| const PresentationTimeRecorderInternal&) = delete; | |
| // Start recording next frame. It skips requesting next frame and returns | |
| // false if the previous frame has not been committed yet. | |
| bool RequestNext(); | |
| // ui::CompositorObserver: | |
| void OnCompositingDidCommit(ui::Compositor* compositor) override { | |
| // Skip updating the state if commit happened after present without | |
| // request because the commit is for unrelated activity. | |
| if (state_ != PRESENTED) | |
| state_ = COMMITTED; | |
| } | |
| void OnCompositingShuttingDown(ui::Compositor* compositor) override { | |
| DCHECK_EQ(compositor_, compositor); | |
| compositor_->RemoveObserver(this); | |
| compositor_ = nullptr; | |
| if (!recording_) | |
| SelfDestruct(); | |
| } | |
| // Mark the recorder to be deleted when the last presentation feedback | |
| // is reported. | |
| void EndRecording() { | |
| recording_ = false; | |
| const bool has_compositing_shut_down = !compositor_; | |
| // If compositing has shut down and the frame hasn't been presented yet, | |
| // it won't happen at this point. This class must self destruct in this case | |
| // otherwise it will leak. | |
| if (state_ == PRESENTED || has_compositing_shut_down) { | |
| SelfDestruct(); | |
| } | |
| } | |
| protected: | |
| int max_latency_ms() const { return max_latency_ms_; } | |
| int present_count() const { return present_count_; } | |
| ~PresentationTimeRecorderInternal() override { | |
| DCHECK(!recording_); | |
| const int average_latency_ms = | |
| present_count_ ? total_latency_ms_ / present_count_ : 0; | |
| VLOG(1) << "Finished Recording FrameTime: average latency=" | |
| << average_latency_ms << "ms, max latency=" << max_latency_ms_ | |
| << "ms"; | |
| if (compositor_) { | |
| compositor_->RemoveObserver(this); | |
| } | |
| } | |
| private: | |
| friend class TestApi; | |
| enum State { | |
| // The frame has been presented to the screen. This is the initial state. | |
| PRESENTED, | |
| // The presentation feedback has been requested. | |
| REQUESTED, | |
| // The changes to layers have been submitted, and waiting to be presented. | |
| COMMITTED, | |
| }; | |
| class Deleter { | |
| public: | |
| explicit Deleter(PresentationTimeRecorderInternal* recorder_internal) | |
| : recorder_internal_(recorder_internal) {} | |
| Deleter(const Deleter&) = delete; | |
| Deleter& operator=(const Deleter&) = delete; | |
| ~Deleter() { recorder_internal_.ExtractAsDangling()->SelfDestruct(); } | |
| private: | |
| raw_ptr<PresentationTimeRecorderInternal> recorder_internal_ = nullptr; | |
| }; | |
| // |delta| is the duration between the successful request time and | |
| // presentation time. | |
| virtual void ReportTime(base::TimeDelta delta) = 0; | |
| void OnPresented(int count, | |
| base::TimeTicks requested_time, | |
| const viz::FrameTimingDetails& frame_timing_details); | |
| void SelfDestruct(); | |
| State state_ = PRESENTED; | |
| int present_count_ = 0; | |
| int request_count_ = 0; | |
| int total_latency_ms_ = 0; | |
| int max_latency_ms_ = 0; | |
| raw_ptr<ui::Compositor> compositor_ = nullptr; | |
| bool recording_ = true; | |
| base::WeakPtrFactory<PresentationTimeRecorderInternal> weak_ptr_factory_{ | |
| this}; | |
| }; | |
| bool PresentationTimeRecorder::PresentationTimeRecorderInternal::RequestNext() { | |
| if (!compositor_) | |
| return false; | |
| if (state_ == REQUESTED) | |
| return false; | |
| const base::TimeTicks now = base::TimeTicks::Now(); | |
| VLOG(1) << "Start Next (" << request_count_ | |
| << ") state=" << (state_ == COMMITTED ? "Committed" : "Presented"); | |
| state_ = REQUESTED; | |
| if (report_immediately_for_test) { | |
| state_ = COMMITTED; | |
| viz::FrameTimingDetails details; | |
| details.presentation_feedback.timestamp = now; | |
| OnPresented(request_count_++, now, details); | |
| return true; | |
| } | |
| compositor_->RequestSuccessfulPresentationTimeForNextFrame( | |
| base::BindOnce(&PresentationTimeRecorderInternal::OnPresented, | |
| weak_ptr_factory_.GetWeakPtr(), request_count_++, now)); | |
| return true; | |
| } | |
| void PresentationTimeRecorder::PresentationTimeRecorderInternal::OnPresented( | |
| int count, | |
| base::TimeTicks requested_time, | |
| const viz::FrameTimingDetails& frame_timing_details) { | |
| base::TimeTicks presentation_timestamp = | |
| frame_timing_details.presentation_feedback.timestamp; | |
| std::optional<Deleter> deleter; | |
| if (!recording_ && (count == (request_count_ - 1))) | |
| deleter.emplace(this); | |
| if (state_ == COMMITTED) | |
| state_ = PRESENTED; | |
| if (presentation_timestamp.is_null()) { | |
| // TODO(b/165951963): ideally feedback.timestamp should not be null. | |
| // Consider replacing this by DCHECK or CHECK. | |
| LOG(ERROR) << "Invalid feedback timestamp (" << count << "):" | |
| << " timestamp is not set"; | |
| return; | |
| } | |
| const base::TimeDelta delta = presentation_timestamp - requested_time; | |
| if (delta.InMilliseconds() < 0) { | |
| LOG(ERROR) << "Invalid timestamp for presentation feedback (" << count | |
| << "): requested_time=" << requested_time | |
| << " presentation_timestamp=" << presentation_timestamp; | |
| return; | |
| } | |
| if (delta.InMilliseconds() > max_latency_ms_) | |
| max_latency_ms_ = delta.InMilliseconds(); | |
| present_count_++; | |
| total_latency_ms_ += delta.InMilliseconds(); | |
| ReportTime(delta); | |
| VLOG(1) << "OnPresented (" << count << "):" << delta.InMilliseconds(); | |
| } | |
| void PresentationTimeRecorder::PresentationTimeRecorderInternal:: | |
| SelfDestruct() { | |
| delete this; | |
| } | |
| // PresentationTimeRecorder --------------------------------------------------- | |
| PresentationTimeRecorder::PresentationTimeRecorder( | |
| raw_ptr<PresentationTimeRecorderInternal> internal) | |
| : recorder_internal_(std::move(internal)) {} | |
| PresentationTimeRecorder::~PresentationTimeRecorder() { | |
| // The internal recorder self destruct when finished its job. | |
| recorder_internal_.ExtractAsDangling()->EndRecording(); | |
| } | |
| bool PresentationTimeRecorder::RequestNext() { | |
| return recorder_internal_->RequestNext(); | |
| } | |
| // static | |
| void PresentationTimeRecorder::SetReportPresentationTimeImmediatelyForTest( | |
| bool enable) { | |
| report_immediately_for_test = enable; | |
| } | |
| namespace { | |
| base::HistogramBase* CreateTimesHistogram( | |
| const char* name, | |
| const PresentationTimeRecorder::BucketParams& bucket_params) { | |
| return base::Histogram::FactoryTimeGet( | |
| name, bucket_params.min_latency, bucket_params.max_latency, | |
| bucket_params.num_buckets, | |
| base::HistogramBase::kUmaTargetedHistogramFlag); | |
| } | |
| // PresentationTimeHistogramRecorder ------------------------------------------ | |
| class PresentationTimeHistogramRecorder | |
| : public PresentationTimeRecorder::PresentationTimeRecorderInternal { | |
| public: | |
| // |presentation_time_histogram_name| records latency reported on | |
| // |ReportTime()|. If |max_latency_histogram_name| is not empty, it records | |
| // the maximum latency reported during the lifetime of this object. Histogram | |
| // names must be the name of the UMA histogram defined in histograms.xml. | |
| PresentationTimeHistogramRecorder( | |
| ui::Compositor* compositor, | |
| const char* presentation_time_histogram_name, | |
| const char* max_latency_histogram_name, | |
| const PresentationTimeRecorder::BucketParams& bucket_params, | |
| bool emit_trace_event) | |
| : PresentationTimeRecorderInternal(compositor), | |
| presentation_time_histogram_( | |
| CreateTimesHistogram(presentation_time_histogram_name, | |
| bucket_params)), | |
| max_latency_histogram_name_(max_latency_histogram_name), | |
| bucket_params_(bucket_params), | |
| presentation_time_histogram_name_( | |
| emit_trace_event ? presentation_time_histogram_name : nullptr) { | |
| if (presentation_time_histogram_name_) { | |
| TRACE_EVENT_BEGIN( | |
| "ui", perfetto::StaticString(presentation_time_histogram_name_), | |
| perfetto::Track::FromPointer(this)); | |
| } | |
| } | |
| PresentationTimeHistogramRecorder(const PresentationTimeHistogramRecorder&) = | |
| delete; | |
| PresentationTimeHistogramRecorder& operator=( | |
| const PresentationTimeHistogramRecorder&) = delete; | |
| // PresentationTimeRecorderInternal: | |
| void ReportTime(base::TimeDelta delta) override { | |
| if (presentation_time_histogram_name_) { | |
| TRACE_EVENT_END("ui", /*presentation_time_histogram_name_*/ | |
| perfetto::Track::FromPointer(this)); | |
| } | |
| presentation_time_histogram_->AddTimeMillisecondsGranularity(delta); | |
| } | |
| private: | |
| ~PresentationTimeHistogramRecorder() override { | |
| if (present_count() > 0 && !max_latency_histogram_name_.empty()) { | |
| CreateTimesHistogram(max_latency_histogram_name_.c_str(), bucket_params_) | |
| ->AddTimeMillisecondsGranularity( | |
| base::Milliseconds(max_latency_ms())); | |
| } | |
| } | |
| raw_ptr<base::HistogramBase> presentation_time_histogram_; | |
| std::string max_latency_histogram_name_; | |
| const PresentationTimeRecorder::BucketParams bucket_params_; | |
| // Only set if `emit_trace_event_` is true since that's its only use. | |
| const char* const presentation_time_histogram_name_ = nullptr; | |
| }; | |
| } // namespace | |
| // BucketParams ------------------------------------------ | |
| PresentationTimeRecorder::BucketParams::BucketParams() = default; | |
| PresentationTimeRecorder::BucketParams::BucketParams( | |
| base::TimeDelta min_latency, | |
| base::TimeDelta max_latency, | |
| int num_buckets) | |
| : min_latency(min_latency), | |
| max_latency(max_latency), | |
| num_buckets(num_buckets) {} | |
| PresentationTimeRecorder::BucketParams::BucketParams(const BucketParams&) = | |
| default; | |
| PresentationTimeRecorder::BucketParams& | |
| PresentationTimeRecorder::BucketParams::operator=(const BucketParams&) = | |
| default; | |
| PresentationTimeRecorder::BucketParams::~BucketParams() = default; | |
| // static | |
| PresentationTimeRecorder::BucketParams | |
| PresentationTimeRecorder::BucketParams::CreateWithMaximum( | |
| base::TimeDelta max_latency) { | |
| BucketParams params; | |
| params.max_latency = max_latency; | |
| return params; | |
| } | |
| std::unique_ptr<PresentationTimeRecorder> | |
| CreatePresentationTimeHistogramRecorder( | |
| ui::Compositor* compositor, | |
| const char* presentation_time_histogram_name, | |
| const char* max_latency_histogram_name, | |
| PresentationTimeRecorder::BucketParams bucket_params, | |
| bool emit_trace_event) { | |
| return std::make_unique<PresentationTimeRecorder>( | |
| new PresentationTimeHistogramRecorder( | |
| compositor, presentation_time_histogram_name, | |
| max_latency_histogram_name, bucket_params, emit_trace_event)); | |
| } | |
| // TestApi -------------------------------------------------------------------- | |
| PresentationTimeRecorder::TestApi::TestApi(PresentationTimeRecorder* recorder) | |
| : recorder_(recorder) {} | |
| void PresentationTimeRecorder::TestApi::OnCompositingDidCommit( | |
| ui::Compositor* compositor) { | |
| recorder_->recorder_internal_->OnCompositingDidCommit(compositor); | |
| } | |
| void PresentationTimeRecorder::TestApi::OnPresented( | |
| int count, | |
| base::TimeTicks requested_time, | |
| const viz::FrameTimingDetails& frame_timing_details) { | |
| recorder_->recorder_internal_->OnPresented(count, requested_time, | |
| frame_timing_details); | |
| } | |
| } // namespace ui |