| // Copyright 2022 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 "components/exo/shell_surface_presentation_time_recorder.h" |
| |
| #include <cstdint> |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/typed_macros.h" |
| #include "components/exo/shell_surface.h" |
| #include "components/exo/shell_surface_util.h" |
| #include "components/exo/surface.h" |
| #include "third_party/perfetto/include/perfetto/tracing/track.h" |
| #include "ui/gfx/presentation_feedback.h" |
| |
| namespace exo { |
| namespace { |
| |
| constexpr char kTraceCategory[] = "benchmark,ui"; |
| |
| base::HistogramBase* CreateTimesHistogram(const char* name) { |
| return base::Histogram::FactoryTimeGet( |
| name, base::Milliseconds(1), base::Milliseconds(200), 50, |
| base::HistogramBase::kUmaTargetedHistogramFlag); |
| } |
| |
| // HistogramReporter reports latency and optional max latency as UMA histograms. |
| class HistogramReporter |
| : public ShellSurfacePresentationTimeRecorder::Reporter { |
| public: |
| HistogramReporter(const char* latency_histogram_name, |
| absl::optional<const char*> max_latency_histogram_name) |
| : latency_histogram_(CreateTimesHistogram(latency_histogram_name)), |
| max_latency_histogram_name_(max_latency_histogram_name) {} |
| |
| HistogramReporter(const HistogramReporter&) = delete; |
| HistogramReporter& operator=(const HistogramReporter&) = delete; |
| |
| ~HistogramReporter() override { |
| if (max_latency_histogram_name_.has_value()) { |
| CreateTimesHistogram(max_latency_histogram_name_.value()) |
| ->AddTimeMillisecondsGranularity(max_latency_); |
| } |
| } |
| |
| // PresentationTimeRecorder::Reporter |
| void ReportTime(base::TimeDelta delta) override { |
| latency_histogram_->AddTimeMillisecondsGranularity(delta); |
| |
| if (max_latency_histogram_name_.has_value() && delta > max_latency_) { |
| max_latency_ = delta; |
| } |
| } |
| |
| private: |
| base::HistogramBase* const latency_histogram_; |
| const absl::optional<const char*> max_latency_histogram_name_; |
| base::TimeDelta max_latency_; |
| }; |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<ShellSurfacePresentationTimeRecorder::Reporter> |
| ShellSurfacePresentationTimeRecorder::CreateHistogramReporter( |
| const char* latency_histogram_name, |
| absl::optional<const char*> max_latency_histogram_name) { |
| return std::make_unique<HistogramReporter>(latency_histogram_name, |
| max_latency_histogram_name); |
| } |
| |
| ShellSurfacePresentationTimeRecorder::ShellSurfacePresentationTimeRecorder( |
| ShellSurface* shell_surface, |
| std::unique_ptr<Reporter> reporter) |
| : shell_surface_(shell_surface), reporter_(std::move(reporter)) { |
| scoped_observation_.Observe(shell_surface_); |
| } |
| |
| ShellSurfacePresentationTimeRecorder::~ShellSurfacePresentationTimeRecorder() = |
| default; |
| |
| void ShellSurfacePresentationTimeRecorder::PrepareToRecord() { |
| if (pending_request_.has_value()) |
| return; |
| |
| pending_request_ = absl::make_optional<Request>(); |
| pending_request_->request_id = next_request_id_++; |
| } |
| |
| bool ShellSurfacePresentationTimeRecorder::RequestNext() { |
| // Underlying ShellSurface must still be alive. |
| DCHECK(shell_surface_); |
| // `PrepareToRecord()` must have happened. |
| DCHECK(pending_request_.has_value()); |
| |
| // Early out if there is a pending request that does not get a Configure. |
| if (!pending_request_->serial.has_value()) |
| return false; |
| |
| TRACE_EVENT_BEGIN(kTraceCategory, "ShellSurfacePresentationTimeRecorder", |
| perfetto::Track(pending_request_->request_id), "serial", |
| pending_request_->serial.value()); |
| |
| pending_request_->request_time = base::TimeTicks::Now(); |
| requests_.emplace_back(pending_request_.value()); |
| pending_request_.reset(); |
| |
| LOG_IF(WARNING, requests_.size() > 100u) |
| << "Number of requests waiting for ack has reached: " << requests_.size(); |
| |
| return true; |
| } |
| |
| void ShellSurfacePresentationTimeRecorder::OnConfigure(uint32_t serial) { |
| if (!pending_request_.has_value()) |
| return; |
| |
| pending_request_->serial = serial; |
| } |
| |
| void ShellSurfacePresentationTimeRecorder::OnAcknowledgeConfigure( |
| uint32_t serial) { |
| // Must not ack a serial in `pending_request_`. RequestNext() should happen |
| // to commit the `pending_request_` before the ack. |
| DCHECK(!pending_request_.has_value() || |
| !pending_request_->serial.has_value() || |
| serial > pending_request_->serial); |
| |
| Surface* root_surface = shell_surface_->root_surface(); |
| while (!requests_.empty()) { |
| Request request = requests_.front(); |
| requests_.pop_front(); |
| |
| root_surface->RequestPresentationCallback(base::BindRepeating( |
| &ShellSurfacePresentationTimeRecorder::OnFramePresented, |
| weak_ptr_factory_.GetWeakPtr(), request)); |
| |
| if (request.serial.value() == serial) |
| break; |
| } |
| } |
| |
| void ShellSurfacePresentationTimeRecorder::OnShellSurfaceDestroyed() { |
| scoped_observation_.Reset(); |
| } |
| |
| void ShellSurfacePresentationTimeRecorder::OnFramePresented( |
| const Request& request, |
| const gfx::PresentationFeedback& feedback) { |
| TRACE_EVENT_END(kTraceCategory, perfetto::Track(request.request_id), "flags", |
| feedback.flags, "serial", request.serial.value()); |
| |
| if (feedback.flags & gfx::PresentationFeedback::kFailure) { |
| LOG(WARNING) << "PresentationFailed (serial=" << *request.serial << "):" |
| << ", flags=" << feedback.flags; |
| return; |
| } |
| |
| if (feedback.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 (serial=" << *request.serial |
| << "):" |
| << " timestamp is not set"; |
| return; |
| } |
| |
| const base::TimeDelta delta = feedback.timestamp - request.request_time; |
| if (delta.InMilliseconds() < 0) { |
| LOG(ERROR) << "Invalid timestamp for presentation feedback (serial=" |
| << *request.serial |
| << "): requested_time=" << request.request_time |
| << " feedback.timestamp=" << feedback.timestamp; |
| return; |
| } |
| |
| reporter_->ReportTime(delta); |
| } |
| |
| } // namespace exo |