text: // 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/ash/arc/tracing/arc_app_performance_tracing_session.h"
  
  #include "base/bind.h"
  #include "base/numerics/safe_conversions.h"
  #include "chrome/browser/ash/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 auto kTargetFrameTime = base::Seconds(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::GetShellRootSurface(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::GetShellRootSurface(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 =
          base::ClampRound<uint64_t>(frame_delta / 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 =
          base::ClampRound<uint64_t>(frame_delta / 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] / frame_deltas_[upper_position];
  
    const double fps = frame_deltas_.size() / tracing_period.InSecondsF();
  
    OnTracingDone(fps, commit_deviation, render_quality);
  }
  
  }  // namespace arc
  