// Copyright 2018 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 CONTENT_BROWSER_SCHEDULER_RESPONSIVENESS_CALCULATOR_H_
#define CONTENT_BROWSER_SCHEDULER_RESPONSIVENESS_CALCULATOR_H_

#include <vector>

#include "base/macros.h"
#include "base/synchronization/lock.h"
#include "base/time/time.h"
#include "content/common/content_export.h"

namespace content {
namespace responsiveness {

// This class receives execution latency on events and tasks, and uses that to
// estimate responsiveness.
//
// All members are UI-thread affine, with the exception of
// |janks_on_io_thread_| which is protected by |io_thread_lock_|.
class CONTENT_EXPORT Calculator {
 public:
  Calculator();
  virtual ~Calculator();

  // Must be called from the UI thread.
  // virtual for testing.
  // The implementation will gracefully handle calls where finish_time <
  // schedule_time.
  // The implementation will gracefully handle successive calls with
  // |schedule_times| that are out of order.
  virtual void TaskOrEventFinishedOnUIThread(base::TimeTicks schedule_time,
                                             base::TimeTicks finish_time);

  // Must be called from the IO thread.
  // virtual for testing.
  virtual void TaskOrEventFinishedOnIOThread(base::TimeTicks schedule_time,
                                             base::TimeTicks finish_time);

  // Each janking task/event is fully defined by |start_time| and |end_time|.
  // Note that |duration| = |end_time| - |start_time|.
  struct Jank {
    Jank(base::TimeTicks start_time, base::TimeTicks end_time);

    base::TimeTicks start_time;
    base::TimeTicks end_time;
  };

 protected:
  // Emits an UMA metric for responsiveness of a single measurement interval.
  // Exposed for testing.
  virtual void EmitResponsiveness(int janky_slices);

  // Exposed for testing.
  base::TimeTicks GetLastCalculationTime();

 private:
  using JankList = std::vector<Jank>;

  // If sufficient time has passed since the last calculation, then calculate
  // responsiveness again and update |last_calculation_time_|.
  //
  // We only trigger this from the UI thread since triggering it from the IO
  // thread would require us to grab the lock, which could cause contention. We
  // only need this to trigger every 30s or so, and we generally expect there to
  // be some activity on the UI thread if Chrome is actually in use.
  void CalculateResponsivenessIfNecessary(base::TimeTicks current_time);

  // Responsiveness is calculated by:
  //   1) Discretizing time into small intervals.
  //   2) In each interval, looking to see if there is a Janky event. If so, the
  //   interval is marked as |janky|.
  //   3) Computing the percentage of intervals that are janky.
  // The caller guarantees that Jank.end_time < |end_time|.
  //
  // This method intentionally takes a std::vector<JankList>, as we may want to
  // extend it in the future to take JankLists from other threads/processes.
  void CalculateResponsiveness(
      std::vector<JankList> janks_from_multiple_threads,
      base::TimeTicks start_time,
      base::TimeTicks end_time);

  // Accessor for |janks_on_ui_thread_|. Must be called from the UI thread.
  JankList& GetJanksOnUIThread();

  // Accessor for |janks_on_io_thread_|. Requires that |io_thread_lock_| has
  // already been taken. May be called from any thread.
  JankList& GetJanksOnIOThread();

  // This method:
  //   1) Removes all Janks with Jank.end_time < |end_time| from |janks|.
  //   2) Returns all Janks with Jank.start_time < |end_time|.
  JankList TakeJanksOlderThanTime(JankList* janks, base::TimeTicks end_time);

  // This should only be accessed via the accessor, which checks that the caller
  // is on the UI thread.
  JankList janks_on_ui_thread_;

  // We expect there to be low contention and this lock to cause minimal
  // overhead. If performance of this lock proves to be a problem, we can move
  // to a lock-free data structure.
  base::Lock io_thread_lock_;

  // This should only be accessed via the accessor, which checks that
  // |io_thread_lock_| has been acquired.
  JankList janks_on_io_thread_;

  // The last time at which metrics were emitted. All janks older than this time
  // have been consumed. Newer janks are still in their JankLists waiting to be
  // consumed.
  base::TimeTicks last_calculation_time_;

  // This class keeps track of the time at which any activity occurred on the UI
  // thread. If a sufficiently long period of time passes without any activity,
  // then it's assumed that the process was suspended. In this case, we should
  // not emit any responsiveness metrics.
  //
  // Note that the process may be suspended while a task or event is being
  // executed, so a very long execution time should be treated similarly.
  base::TimeTicks most_recent_activity_time_;

  // The number of times the responsiveness metric has been emitted.
  int emission_count_ = 0;

  DISALLOW_COPY_AND_ASSIGN(Calculator);
};

}  // namespace responsiveness
}  // namespace content

#endif  // CONTENT_BROWSER_SCHEDULER_RESPONSIVENESS_CALCULATOR_H_
