Commit ca3ee459 authored by Mitsuru Oshima's avatar Mitsuru Oshima Committed by Chromium LUCI CQ

TotalAnimationThroughputReporter

This class measures the throughput from the time first animation
is started on a compositor, to the time last animation ended.

This is useful to measure the total animation throughput during
a transition that has many animations applied such as login, overview
etc.

Bug: 1150492
Test: covered by unittest

Change-Id: Id0bfec6e57403b7f1ebe5a8068ae095b1412bc7d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2550366
Commit-Queue: Mitsuru Oshima <oshima@chromium.org>
Reviewed-by: default avatarRobert Flack <flackr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#833625}
parent edb72404
...@@ -69,6 +69,8 @@ component("compositor") { ...@@ -69,6 +69,8 @@ component("compositor") {
"throughput_tracker.cc", "throughput_tracker.cc",
"throughput_tracker.h", "throughput_tracker.h",
"throughput_tracker_host.h", "throughput_tracker_host.h",
"total_animation_throughput_reporter.cc",
"total_animation_throughput_reporter.h",
"transform_animation_curve_adapter.cc", "transform_animation_curve_adapter.cc",
"transform_animation_curve_adapter.h", "transform_animation_curve_adapter.h",
"transform_recorder.cc", "transform_recorder.cc",
...@@ -238,6 +240,7 @@ test("compositor_unittests") { ...@@ -238,6 +240,7 @@ test("compositor_unittests") {
"layer_owner_unittest.cc", "layer_owner_unittest.cc",
"layer_unittest.cc", "layer_unittest.cc",
"run_all_unittests.cc", "run_all_unittests.cc",
"total_animation_throughput_reporter_unittest.cc",
"transform_animation_curve_adapter_unittest.cc", "transform_animation_curve_adapter_unittest.cc",
] ]
......
...@@ -576,13 +576,23 @@ bool Compositor::HasObserver(const CompositorObserver* observer) const { ...@@ -576,13 +576,23 @@ bool Compositor::HasObserver(const CompositorObserver* observer) const {
} }
void Compositor::AddAnimationObserver(CompositorAnimationObserver* observer) { void Compositor::AddAnimationObserver(CompositorAnimationObserver* observer) {
if (!animation_observer_list_.has_observers()) {
for (auto& obs : observer_list_)
obs.OnFirstAnimationStarted(this);
}
animation_observer_list_.AddObserver(observer); animation_observer_list_.AddObserver(observer);
host_->SetNeedsAnimate(); host_->SetNeedsAnimate();
} }
void Compositor::RemoveAnimationObserver( void Compositor::RemoveAnimationObserver(
CompositorAnimationObserver* observer) { CompositorAnimationObserver* observer) {
if (!animation_observer_list_.HasObserver(observer))
return;
animation_observer_list_.RemoveObserver(observer); animation_observer_list_.RemoveObserver(observer);
if (!animation_observer_list_.has_observers()) {
for (auto& obs : observer_list_)
obs.OnLastAnimationEnded(this);
}
} }
bool Compositor::HasAnimationObserver( bool Compositor::HasAnimationObserver(
......
...@@ -409,6 +409,7 @@ class COMPOSITOR_EXPORT Compositor : public cc::LayerTreeHostClient, ...@@ -409,6 +409,7 @@ class COMPOSITOR_EXPORT Compositor : public cc::LayerTreeHostClient,
private: private:
friend class base::RefCounted<Compositor>; friend class base::RefCounted<Compositor>;
friend class TotalAnimationThroughputReporter;
// Called when collected metrics for the tracker of |tracker_id| is ready. // Called when collected metrics for the tracker of |tracker_id| is ready.
void ReportMetricsForTracker( void ReportMetricsForTracker(
......
...@@ -60,6 +60,9 @@ class COMPOSITOR_EXPORT CompositorObserver { ...@@ -60,6 +60,9 @@ class COMPOSITOR_EXPORT CompositorObserver {
virtual void OnDidPresentCompositorFrame( virtual void OnDidPresentCompositorFrame(
uint32_t frame_token, uint32_t frame_token,
const gfx::PresentationFeedback& feedback) {} const gfx::PresentationFeedback& feedback) {}
virtual void OnFirstAnimationStarted(Compositor* compositor) {}
virtual void OnLastAnimationEnded(Compositor* compositor) {}
}; };
} // namespace ui } // namespace ui
......
// Copyright 2020 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 "ui/compositor/total_animation_throughput_reporter.h"
#include "base/logging.h"
#include "ui/compositor/compositor.h"
namespace ui {
TotalAnimationThroughputReporter::TotalAnimationThroughputReporter(
ui::Compositor* compositor,
ReportOnceCallback once_callback,
bool should_delete)
: TotalAnimationThroughputReporter(compositor,
ReportRepeatingCallback(),
std::move(once_callback),
should_delete) {}
TotalAnimationThroughputReporter::TotalAnimationThroughputReporter(
ui::Compositor* compositor,
ReportRepeatingCallback repeating_callback)
: TotalAnimationThroughputReporter(compositor,
repeating_callback,
ReportOnceCallback(),
/*should_delete=*/false) {}
TotalAnimationThroughputReporter::~TotalAnimationThroughputReporter() {
if (throughput_tracker_)
throughput_tracker_->Cancel();
if (compositor_)
compositor_->RemoveObserver(this);
}
void TotalAnimationThroughputReporter::OnFirstAnimationStarted(
ui::Compositor* compositor) {
throughput_tracker_ = compositor->RequestNewThroughputTracker();
throughput_tracker_->Start(base::BindRepeating(
&TotalAnimationThroughputReporter::Report, ptr_factory_.GetWeakPtr()));
}
void TotalAnimationThroughputReporter::OnLastAnimationEnded(
ui::Compositor* compositor) {
throughput_tracker_->Stop();
throughput_tracker_.reset();
}
void TotalAnimationThroughputReporter::OnCompositingShuttingDown(
ui::Compositor* compositor) {
if (throughput_tracker_) {
throughput_tracker_->Cancel();
throughput_tracker_.reset();
}
compositor->RemoveObserver(this);
compositor_ = nullptr;
if (should_delete_)
delete this;
}
TotalAnimationThroughputReporter::TotalAnimationThroughputReporter(
ui::Compositor* compositor,
ReportRepeatingCallback repeating_callback,
ReportOnceCallback once_callback,
bool should_delete)
: compositor_(compositor),
report_repeating_callback_(repeating_callback),
report_once_callback_(std::move(once_callback)),
should_delete_(should_delete) {
DCHECK_NE(report_repeating_callback_.is_null(),
report_once_callback_.is_null());
compositor_->AddObserver(this);
if (compositor->animation_observer_list_.has_observers())
OnFirstAnimationStarted(compositor_);
}
void TotalAnimationThroughputReporter::Report(
const cc::FrameSequenceMetrics::CustomReportData& data) {
if (!report_once_callback_.is_null()) {
compositor_->RemoveObserver(this);
std::move(report_once_callback_).Run(data);
if (should_delete_)
delete this;
return;
}
if (!report_repeating_callback_.is_null())
report_repeating_callback_.Run(data);
}
} // namespace ui
// Copyright 2020 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.
#ifndef UI_COMPOSITOR_TOTAL_ANIMATION_THROUGHPUT_REPORTER_H_
#define UI_COMPOSITOR_TOTAL_ANIMATION_THROUGHPUT_REPORTER_H_
#include <memory>
#include "base/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "cc/metrics/frame_sequence_metrics.h"
#include "ui/compositor/compositor_export.h"
#include "ui/compositor/compositor_observer.h"
#include "ui/compositor/throughput_tracker.h"
namespace ui {
// Reports cc::FrameSequenceMetrics::ThroughputData between the first animation
// start and the last animation ends on a compositor.
//
// Please see AnimationThroughputReporter for the definition of the throughput
// and jack metrics.
//
// See also docs/speed/graphics_metrics_definitions.md.
//
// cc::FrameSequenceMetrics::CustomReportData contains the numbers of produced
// frames, expected frames and jank count.
//
// The tracking starts when the first animation observer is added to the
// compositor, then stopped when the last animation observer is removed. The
// report callback is invoked on the next begin frame if there is enough data.
// Since this observes multiple animations, aborting one of animations will
// not cancel the tracking, and the data will be reported as normal.
class COMPOSITOR_EXPORT TotalAnimationThroughputReporter
: public CompositorObserver {
public:
using ReportOnceCallback = base::OnceCallback<void(
const cc::FrameSequenceMetrics::CustomReportData& data)>;
using ReportRepeatingCallback = base::RepeatingCallback<void(
const cc::FrameSequenceMetrics::CustomReportData& data)>;
// Create a TotalAnimationThroughputReporter that observes
// the total animation throughput just once. If |should_delete|
// is true, then the object will be deleted after callback is
// invoked.
TotalAnimationThroughputReporter(Compositor* compositor,
ReportOnceCallback callback,
bool should_delete);
// Create a persistent TotalAnimationThroughputReporter, which
// will call the callback every time the last animation is finished.
TotalAnimationThroughputReporter(Compositor* compositor,
ReportRepeatingCallback callback);
TotalAnimationThroughputReporter(const TotalAnimationThroughputReporter&) =
delete;
TotalAnimationThroughputReporter& operator=(
const TotalAnimationThroughputReporter&) = delete;
~TotalAnimationThroughputReporter() override;
// CompositorObserver:
void OnFirstAnimationStarted(Compositor* compositor) override;
void OnLastAnimationEnded(Compositor* compositor) override;
void OnCompositingShuttingDown(Compositor* compositor) override;
bool IsMeasuringForTesting() const { return bool{throughput_tracker_}; }
private:
TotalAnimationThroughputReporter(Compositor* compositor,
ReportRepeatingCallback repeating_callback,
ReportOnceCallback once_callback,
bool should_delete);
void Report(const cc::FrameSequenceMetrics::CustomReportData& data);
Compositor* compositor_;
ReportRepeatingCallback report_repeating_callback_;
ReportOnceCallback report_once_callback_;
bool should_delete_ = false;
base::Optional<ThroughputTracker> throughput_tracker_;
base::WeakPtrFactory<TotalAnimationThroughputReporter> ptr_factory_{this};
};
} // namespace ui
#endif // UI_COMPOSITOR_TOTAL_ANIMATION_THROUGHPUT_REPORTER_H_
// Copyright 2020 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 "ui/compositor/total_animation_throughput_reporter.h"
#include <memory>
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "cc/metrics/frame_sequence_metrics.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/compositor/test/animation_throughput_reporter_test_base.h"
#include "ui/gfx/geometry/rect.h"
#if defined(OS_WIN)
// TestCompositor doesn't work with MOCK_TIME on Windows. crbug.com/1152103
#define MAYBE_SingleAnimation DISABLED_SingleAnimation
#define MAYBE_StopAnimation DISABLED_StopAnimation
#define MAYBE_MultipleAnimations DISABLED_MultipleAnimations
#define MAYBE_MultipleAnimationsOnSingleLayer \
DISABLED_MultipleAnimationsOnSingleLayer
#define MAYBE_AddAnimationWhileAnimating DISABLED_AddAnimationWhileAnimating
#define MAYBE_RemoveWhileAnimating DISABLED_RemoveWhileAnimating
#define MAYBE_StartWhileAnimating DISABLED_StartWhileAnimating
#define MAYBE_PersistedAnimation DISABLED_PersistedAnimation
#define MAYBE_OnceReporter DISABLED_OnceReporter
#define MAYBE_OnceReporterShouldDelete DISABLED_OnceReporterShouldDelete
#else
#define MAYBE_SingleAnimation SingleAnimation
#define MAYBE_StopAnimation StopAnimation
#define MAYBE_MultipleAnimations MultipleAnimations
#define MAYBE_MultipleAnimationsOnSingleLayer MultipleAnimationsOnSingleLayer
#define MAYBE_AddAnimationWhileAnimating AddAnimationWhileAnimating
#define MAYBE_RemoveWhileAnimating RemoveWhileAnimating
#define MAYBE_StartWhileAnimating StartWhileAnimating
#define MAYBE_PersistedAnimation PersistedAnimation
#define MAYBE_OnceReporter OnceReporter
#define MAYBE_OnceReporterShouldDelete OnceReporterShouldDelete
#endif
namespace ui {
namespace {
class TestReporter : public TotalAnimationThroughputReporter {
public:
explicit TestReporter(ui::Compositor* compositor)
: ui::TotalAnimationThroughputReporter(
compositor,
base::BindRepeating(&TestReporter::Reported,
base::Unretained(this))) {}
TestReporter(ui::Compositor* compositor, bool should_delete)
: ui::TotalAnimationThroughputReporter(
compositor,
base::BindOnce(&TestReporter::Reported, base::Unretained(this)),
should_delete) {}
TestReporter(const TestReporter&) = delete;
TestReporter& operator=(const TestReporter&) = delete;
~TestReporter() override = default;
bool reported() const { return reported_; }
void reset() { reported_ = false; }
private:
void Reported(const cc::FrameSequenceMetrics::CustomReportData&) {
reported_ = true;
}
bool reported_ = false;
};
} // namespace
using TotalAnimationThroughputReporterTest =
AnimationThroughputReporterTestBase;
TEST_F(TotalAnimationThroughputReporterTest, MAYBE_SingleAnimation) {
Layer layer;
layer.SetOpacity(0.5f);
root_layer()->Add(&layer);
TestReporter reporter(compositor());
{
LayerAnimator* animator = layer.GetAnimator();
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
layer.SetOpacity(1.0f);
}
Advance(base::TimeDelta::FromMilliseconds(32));
EXPECT_FALSE(reporter.reported());
Advance(base::TimeDelta::FromMilliseconds(32));
EXPECT_TRUE(reporter.reported());
}
// Tests the stopping last animation will trigger the animation.
TEST_F(TotalAnimationThroughputReporterTest, MAYBE_StopAnimation) {
Layer layer;
layer.SetOpacity(0.5f);
root_layer()->Add(&layer);
TestReporter reporter(compositor());
{
LayerAnimator* animator = layer.GetAnimator();
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
layer.SetOpacity(1.0f);
}
Advance(base::TimeDelta::FromMilliseconds(16));
EXPECT_FALSE(reporter.reported());
layer.GetAnimator()->StopAnimating();
Advance(base::TimeDelta::FromMilliseconds(16));
EXPECT_TRUE(reporter.reported());
}
// Tests the longest animation will trigger the report.
TEST_F(TotalAnimationThroughputReporterTest, MAYBE_MultipleAnimations) {
Layer layer1;
layer1.SetOpacity(0.5f);
root_layer()->Add(&layer1);
TestReporter reporter(compositor());
{
LayerAnimator* animator = layer1.GetAnimator();
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
layer1.SetOpacity(1.0f);
}
Layer layer2;
layer2.SetOpacity(0.5f);
root_layer()->Add(&layer2);
{
LayerAnimator* animator = layer2.GetAnimator();
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(96));
layer2.SetOpacity(1.0f);
}
Advance(base::TimeDelta::FromMilliseconds(32));
EXPECT_FALSE(reporter.reported());
Advance(base::TimeDelta::FromMilliseconds(32));
EXPECT_FALSE(reporter.reported());
Advance(base::TimeDelta::FromMilliseconds(200));
EXPECT_TRUE(reporter.reported());
}
// Tests the longest animation on a single layer will triger the report.
TEST_F(TotalAnimationThroughputReporterTest,
MAYBE_MultipleAnimationsOnSingleLayer) {
Layer layer;
layer.SetOpacity(0.5f);
layer.SetLayerBrightness(0.5f);
root_layer()->Add(&layer);
TestReporter reporter(compositor());
{
LayerAnimator* animator = layer.GetAnimator();
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
layer.SetOpacity(1.0f);
}
{
LayerAnimator* animator = layer.GetAnimator();
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(96));
layer.SetLayerBrightness(1.0f);
}
Advance(base::TimeDelta::FromMilliseconds(64));
EXPECT_FALSE(reporter.reported());
Advance(base::TimeDelta::FromMilliseconds(48));
EXPECT_TRUE(reporter.reported());
}
// Tests adding new animation will extends the duration.
TEST_F(TotalAnimationThroughputReporterTest, MAYBE_AddAnimationWhileAnimating) {
Layer layer1;
layer1.SetOpacity(0.5f);
root_layer()->Add(&layer1);
TestReporter reporter(compositor());
{
LayerAnimator* animator = layer1.GetAnimator();
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
layer1.SetOpacity(1.0f);
}
Advance(base::TimeDelta::FromMilliseconds(32));
EXPECT_FALSE(reporter.reported());
// Add new animation while animating.
Layer layer2;
layer2.SetOpacity(0.5f);
root_layer()->Add(&layer2);
{
LayerAnimator* animator = layer2.GetAnimator();
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
layer2.SetOpacity(1.0f);
}
// The animation time is extended.
Advance(base::TimeDelta::FromMilliseconds(32));
EXPECT_FALSE(reporter.reported());
Advance(base::TimeDelta::FromMilliseconds(32));
EXPECT_TRUE(reporter.reported());
}
// Tests removing last animation will call report callback.
TEST_F(TotalAnimationThroughputReporterTest, MAYBE_RemoveWhileAnimating) {
auto layer1 = std::make_unique<Layer>();
layer1->SetOpacity(0.5f);
root_layer()->Add(layer1.get());
TestReporter reporter(compositor());
{
LayerAnimator* animator = layer1->GetAnimator();
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(100));
layer1->SetOpacity(1.0f);
}
Layer layer2;
layer2.SetOpacity(0.5f);
root_layer()->Add(&layer2);
{
LayerAnimator* animator = layer2.GetAnimator();
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
layer2.SetOpacity(1.0f);
}
Advance(base::TimeDelta::FromMilliseconds(48));
EXPECT_FALSE(reporter.reported());
layer1.reset();
// Aborting will be processed in next frame.
Advance(base::TimeDelta::FromMilliseconds(16));
EXPECT_TRUE(reporter.reported());
}
// Make sure the reporter can start measuring even if the animation
// has started.
TEST_F(TotalAnimationThroughputReporterTest, MAYBE_StartWhileAnimating) {
Layer layer;
layer.SetOpacity(0.5f);
root_layer()->Add(&layer);
{
LayerAnimator* animator = layer.GetAnimator();
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(48));
layer.SetOpacity(1.0f);
}
Advance(base::TimeDelta::FromMilliseconds(16));
TestReporter reporter(compositor());
EXPECT_TRUE(reporter.IsMeasuringForTesting());
Advance(base::TimeDelta::FromMilliseconds(100));
EXPECT_TRUE(reporter.reported());
}
// Tests the reporter is called multiple times for persistent animation.
TEST_F(TotalAnimationThroughputReporterTest, MAYBE_PersistedAnimation) {
Layer layer;
layer.SetOpacity(0.5f);
root_layer()->Add(&layer);
// Set a persisted animator to |layer|.
LayerAnimator* animator =
new LayerAnimator(base::TimeDelta::FromMilliseconds(48));
layer.SetAnimator(animator);
// |reporter| keeps reporting as long as it is alive.
TestReporter reporter(compositor());
// Report data for animation of opacity goes to 1.
layer.SetOpacity(1.0f);
Advance(base::TimeDelta::FromMilliseconds(100));
EXPECT_TRUE(reporter.reported());
// Report data for animation of opacity goes to 0.5.
reporter.reset();
layer.SetOpacity(0.5f);
Advance(base::TimeDelta::FromMilliseconds(100));
EXPECT_TRUE(reporter.reported());
}
// Make sure the once reporter is called only once.
TEST_F(TotalAnimationThroughputReporterTest, MAYBE_OnceReporter) {
Layer layer;
layer.SetOpacity(0.5f);
root_layer()->Add(&layer);
// Set a persisted animator to |layer|.
LayerAnimator* animator =
new LayerAnimator(base::TimeDelta::FromMilliseconds(32));
layer.SetAnimator(animator);
TestReporter reporter(compositor(), /*should_delete=*/false);
// Report data for animation of opacity goes to 1.
layer.SetOpacity(1.0f);
Advance(base::TimeDelta::FromMilliseconds(100));
EXPECT_TRUE(reporter.reported());
// Report data for animation of opacity goes to 0.5.
reporter.reset();
layer.SetOpacity(1.0f);
Advance(base::TimeDelta::FromMilliseconds(100));
EXPECT_FALSE(reporter.reported());
}
// One reporter marked as "should_delete" should be deleted when
// reported.
TEST_F(TotalAnimationThroughputReporterTest, MAYBE_OnceReporterShouldDelete) {
class DeleteTestReporter : public TotalAnimationThroughputReporter {
public:
DeleteTestReporter(Compositor* compositor,
ReportOnceCallback callback,
bool* deleted)
: TotalAnimationThroughputReporter(compositor,
std::move(callback),
true),
deleted_(deleted) {}
~DeleteTestReporter() override { *deleted_ = true; }
private:
bool* deleted_;
};
Layer layer;
layer.SetOpacity(0.5f);
root_layer()->Add(&layer);
// Set a persisted animator to |layer|.
LayerAnimator* animator =
new LayerAnimator(base::TimeDelta::FromMilliseconds(32));
layer.SetAnimator(animator);
// |reporter| keeps reporting as long as it is alive.
bool reported = false;
bool deleted = false;
new DeleteTestReporter(
compositor(),
base::BindLambdaForTesting(
[&](const cc::FrameSequenceMetrics::CustomReportData&) {
reported = true;
}),
&deleted);
// Report data for animation of opacity goes to 1.
layer.SetOpacity(1.0f);
Advance(base::TimeDelta::FromMilliseconds(100));
EXPECT_TRUE(reported);
EXPECT_TRUE(deleted);
}
} // namespace ui
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment