blob: f1ee3bd99a4283423576c6afa67d9438ff0352a0 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// 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 <set>
#include <vector>
#include "base/synchronization/lock.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/common/content_export.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/application_status_listener.h"
#endif
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 |*_on_io_thread_|
// which are protected by |io_thread_lock_|.
class CONTENT_EXPORT Calculator {
public:
Calculator();
Calculator(const Calculator&) = delete;
Calculator& operator=(const Calculator&) = delete;
virtual ~Calculator();
// Must be called from the UI thread.
// virtual for testing.
// Assumes that |execution_finish_time| is the current time.
// The implementation will gracefully handle successive calls with unordered
// |queue_time|s.
virtual void TaskOrEventFinishedOnUIThread(
base::TimeTicks queue_time,
base::TimeTicks execution_start_time,
base::TimeTicks execution_finish_time);
// Must be called from the IO thread.
// virtual for testing.
// The implementation will gracefully handle successive calls with unordered
// |queue_time|s.
virtual void TaskOrEventFinishedOnIOThread(
base::TimeTicks queue_time,
base::TimeTicks execution_start_time,
base::TimeTicks execution_finish_time);
// Must be invoked once-and-only-once, after the first time the
// MainMessageLoopRun() reaches idle (i.e. done running all tasks queued
// during startup). This will be used as a signal for the true end of
// "startup" and the beginning of recording
// Browser.MainThreadsCongestion.
void OnFirstIdle();
// Change the Power state of the process. Must be called from the UI thread.
void SetProcessSuspended(bool suspended);
// Each congested task/event is fully defined by |start_time| and |end_time|.
// Note that |duration| = |end_time| - |start_time|.
struct Congestion {
Congestion(base::TimeTicks start_time, base::TimeTicks end_time);
base::TimeTicks start_time;
base::TimeTicks end_time;
};
// Types of congestion recorded by this Calculator. Public for testing.
enum class CongestionType {
kExecutionOnly,
kQueueAndExecution,
};
// Stages of startup used by this Calculator. Stages are defined in
// chronological order, some can be skipped. Public for
// testing.
enum class StartupStage {
// Monitoring the first interval.
kFirstInterval,
// First interval completed but it didn't capture OnFirstIdle().
kFirstIntervalDoneWithoutFirstIdle,
// Monitoring the first interval after OnFirstIdle().
kFirstIntervalAfterFirstIdle,
// All intervals after kFirstIntervalAfterFirstIdle.
kPeriodic
};
protected:
// Emits an UMA metric for responsiveness of a single measurement interval.
// Exposed for testing.
virtual void EmitResponsiveness(CongestionType congestion_type,
size_t num_congested_slices,
StartupStage startup_stage);
// Emits trace events for responsiveness metric. A trace event is emitted for
// the whole duration of the metric interval and sub events are emitted for
// the specific congested slices.
// Exposed for testing.
void EmitResponsivenessTraceEvents(CongestionType congestion_type,
base::TimeTicks start_time,
base::TimeTicks end_time,
const std::set<int>& congested_slices);
// Exposed for testing.
virtual void EmitCongestedIntervalsMeasurementTraceEvent(
base::TimeTicks start_time,
base::TimeTicks end_time,
size_t amount_of_slices);
// Exposed for testing.
virtual void EmitCongestedIntervalTraceEvent(base::TimeTicks start_time,
base::TimeTicks end_time);
// Exposed for testing.
base::TimeTicks GetLastCalculationTime();
private:
using CongestionList = std::vector<Congestion>;
// 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 Congestion. If so, the
// interval is marked as |congested|.
// 3) Computing the percentage of intervals that are congested.
//
// This method intentionally takes a std::vector<CongestionList>, as we may
// want to extend it in the future to take CongestionLists from other
// threads/processes.
void CalculateResponsiveness(
CongestionType congestion_type,
std::vector<CongestionList> congestions_from_multiple_threads,
base::TimeTicks start_time,
base::TimeTicks end_time);
// Accessors for |execution_congestion_on_ui_thread_| and
// ||congestion_on_ui_thread_|. Must be called from the UI
// thread.
CongestionList& GetExecutionCongestionOnUIThread();
CongestionList& GetCongestionOnUIThread();
#if BUILDFLAG(IS_ANDROID)
// Callback invoked when the application state changes.
void OnApplicationStateChanged(base::android::ApplicationState state);
#endif
// This helper method:
// 1) Removes all Congestions with Congestion.end_time < |end_time| from
// |congestions|. 2) Returns all Congestions with Congestion.start_time <
// |end_time|.
static CongestionList TakeCongestionsOlderThanTime(
CongestionList* congestions,
base::TimeTicks end_time);
// Congestion from tasks/events with a long execution time on the UI thread.
// Should only be accessed via the accessor, which checks that the caller is
// on the UI thread.
CongestionList execution_congestion_on_ui_thread_;
// Congestion from tasks/events with a long queueing + execution time on the
// UI thread. Should only be accessed via the accessor, which checks that the
// caller is on the UI thread.
CongestionList congestion_on_ui_thread_;
#if BUILDFLAG(IS_ANDROID)
// Stores the current visibility state of the application. Accessed only on
// the UI thread.
bool is_application_visible_ = false;
#endif
StartupStage startup_stage_ = StartupStage::kFirstInterval;
bool past_first_idle_ = false;
// 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_;
// Congestion from tasks/events with a long execution time on the IO thread.
CongestionList execution_congestion_on_io_thread_ GUARDED_BY(io_thread_lock_);
// Congestion from tasks/events with a long queueing + execution time on the
// IO thread.
CongestionList congestion_on_io_thread_ GUARDED_BY(io_thread_lock_);
// The last time at which metrics were emitted. All congestions older than
// this time have been consumed. Newer congestions are still in their
// CongestionLists 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_;
#if BUILDFLAG(IS_ANDROID)
// Listener for changes in application state, unregisters itself when
// destroyed.
const std::unique_ptr<base::android::ApplicationStatusListener>
application_status_listener_;
#endif
};
} // namespace responsiveness
} // namespace content
#endif // CONTENT_BROWSER_SCHEDULER_RESPONSIVENESS_CALCULATOR_H_