blob: 64aef3ffbe50f683edc130eba7833a655f6670d1 [file] [log] [blame]
// Copyright 2019 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 "chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing_session.h"
#include "base/bind.h"
#include "chrome/browser/chromeos/arc/tracing/arc_app_performance_tracing.h"
#include "components/exo/shell_surface_util.h"
#include "components/exo/surface.h"
#include "content/public/browser/browser_thread.h"
#include "ui/aura/window.h"
namespace arc {
namespace {
// Target FPS, all reference devices has 60 FPS.
// TODO(khmel), detect this per device.
constexpr uint64_t kTargetFps = 60;
constexpr base::TimeDelta kTargetFrameTime =
base::TimeDelta::FromSeconds(1) / kTargetFps;
// Used for detection the idle. App considered in idle state when there is no
// any commit for |kIdleThresholdFrames| frames.
constexpr uint64_t kIdleThresholdFrames = 10;
} // namespace
ArcAppPerformanceTracingSession::ArcAppPerformanceTracingSession(
ArcAppPerformanceTracing* owner)
: owner_(owner), window_(owner->active_window()) {
DCHECK(owner_);
DCHECK(window_);
}
ArcAppPerformanceTracingSession::~ArcAppPerformanceTracingSession() {
// Discard any active tracing if any.
Stop();
}
ArcAppPerformanceTracingCustomSession*
ArcAppPerformanceTracingSession::AsCustomSession() {
return nullptr;
}
void ArcAppPerformanceTracingSession::ScheduleInternal(
bool detect_idles,
const base::TimeDelta& start_delay,
const base::TimeDelta& tracing_period) {
DCHECK(!tracing_active_);
DCHECK(!tracing_timer_.IsRunning());
detect_idles_ = detect_idles;
tracing_period_ = tracing_period;
if (start_delay.is_zero()) {
Start();
return;
}
tracing_timer_.Start(FROM_HERE, start_delay,
base::BindOnce(&ArcAppPerformanceTracingSession::Start,
base::Unretained(this)));
}
void ArcAppPerformanceTracingSession::StopAndAnalyzeInternal() {
DCHECK(tracing_active_);
Analyze(base::TimeTicks::Now() - tracing_start_);
}
void ArcAppPerformanceTracingSession::OnSurfaceDestroying(
exo::Surface* surface) {
Stop();
}
void ArcAppPerformanceTracingSession::OnCommit(exo::Surface* surface) {
HandleCommit(base::Time::Now());
}
void ArcAppPerformanceTracingSession::FireTimerForTesting() {
tracing_timer_.FireNow();
}
void ArcAppPerformanceTracingSession::OnCommitForTesting(
const base::Time& timestamp) {
HandleCommit(timestamp);
}
void ArcAppPerformanceTracingSession::Start() {
DCHECK(!tracing_timer_.IsRunning());
VLOG(1) << "Start tracing.";
frame_deltas_.clear();
last_commit_timestamp_ = base::Time();
exo::Surface* const surface = exo::GetShellMainSurface(window_);
DCHECK(surface);
surface->AddSurfaceObserver(this);
// Schedule result analyzing at the end of tracing.
tracing_start_ = base::TimeTicks::Now();
if (!tracing_period_.is_zero()) {
// |tracing_period_| is passed to be able to correctly compare expectations
// in unit tests.
tracing_timer_.Start(
FROM_HERE, tracing_period_,
base::BindOnce(&ArcAppPerformanceTracingSession::Analyze,
base::Unretained(this), tracing_period_));
}
tracing_active_ = true;
}
void ArcAppPerformanceTracingSession::Stop() {
tracing_active_ = false;
tracing_timer_.Stop();
exo::Surface* const surface = exo::GetShellMainSurface(window_);
// Surface might be destroyed.
if (surface)
surface->RemoveSurfaceObserver(this);
}
void ArcAppPerformanceTracingSession::HandleCommit(
const base::Time& timestamp) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (last_commit_timestamp_.is_null()) {
last_commit_timestamp_ = timestamp;
return;
}
const base::TimeDelta frame_delta = timestamp - last_commit_timestamp_;
last_commit_timestamp_ = timestamp;
if (detect_idles_) {
const uint64_t display_frames_passed =
(frame_delta + kTargetFrameTime / 2) / kTargetFrameTime;
if (display_frames_passed >= kIdleThresholdFrames) {
// Idle is detected, try the next time.
Stop();
OnTracingFailed();
return;
}
}
frame_deltas_.emplace_back(frame_delta);
}
void ArcAppPerformanceTracingSession::Analyze(base::TimeDelta tracing_period) {
// No more data is needed, stop active tracing.
Stop();
if (frame_deltas_.empty() || tracing_period <= base::TimeDelta()) {
OnTracingFailed();
return;
}
// Check last commit timestamp if we are in idle at this moment.
if (detect_idles_) {
const base::TimeDelta last_frame_delta =
base::Time::Now() - last_commit_timestamp_;
if (last_frame_delta >= kTargetFrameTime * kIdleThresholdFrames) {
// Current idle state is detected, try next time.
OnTracingFailed();
return;
}
}
VLOG(1) << "Analyze tracing.";
double vsync_error_deviation_accumulator = 0;
for (const auto& frame_delta : frame_deltas_) {
// Calculate the number of display frames passed between two updates.
// Ideally we should have one frame for target FPS. In case the app drops
// frames, the number of dropped frames would be accounted. The result is
// fractional part of target frame interval |kTargetFrameTime| and is less
// or equal half of it.
const uint64_t display_frames_passed =
(frame_delta + kTargetFrameTime / 2) / kTargetFrameTime;
// Calculate difference from the ideal commit time, that should happen with
// equal delay for each display frame.
const base::TimeDelta vsync_error =
frame_delta - display_frames_passed * kTargetFrameTime;
vsync_error_deviation_accumulator +=
(vsync_error.InMicrosecondsF() * vsync_error.InMicrosecondsF());
}
const double commit_deviation =
sqrt(vsync_error_deviation_accumulator / frame_deltas_.size());
std::sort(frame_deltas_.begin(), frame_deltas_.end());
// Get 10% and 90% indices.
const size_t lower_position = frame_deltas_.size() / 10;
const size_t upper_position = frame_deltas_.size() - 1 - lower_position;
const double render_quality =
frame_deltas_[lower_position].InMicrosecondsF() /
frame_deltas_[upper_position].InMicrosecondsF();
const double fps = frame_deltas_.size() / tracing_period.InSecondsF();
OnTracingDone(fps, commit_deviation, render_quality);
}
} // namespace arc