blob: 82b9a859d435c2db0f871f7addcf58a7738ed3a6 [file] [log] [blame]
// 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.
#include "content/browser/scheduler/responsiveness/calculator.h"
#include <algorithm>
#include <set>
#include "base/metrics/histogram_macros.h"
#include "content/public/browser/browser_thread.h"
namespace content {
namespace responsiveness {
namespace {
// We divide the measurement interval into discretized time slices.
// Each slice is marked as janky if it contained a janky task. A janky task is
// one whose execution latency is greater than kJankThreshold.
constexpr base::TimeDelta kMeasurementInterval =
base::TimeDelta::FromSeconds(30);
// A task or event longer than kJankThreshold is considered janky.
constexpr base::TimeDelta kJankThreshold =
base::TimeDelta::FromMilliseconds(100);
// If there have been no events/tasks on the UI thread for a significant period
// of time, it's likely because Chrome was suspended.
// This value is copied from queueing_time_estimator.cc:kInvalidPeriodThreshold.
constexpr base::TimeDelta kSuspendInterval = base::TimeDelta::FromSeconds(30);
// Given a |jank|, finds each janky slice between |start_time| and |end_time|,
// and adds it to |janky_slices|.
void AddJankySlices(std::set<int>* janky_slices,
const Calculator::Jank& jank,
base::TimeTicks start_time,
base::TimeTicks end_time) {
// Ignore the first jank threshold, since that's the part of the task/event
// that wasn't janky.
base::TimeTicks jank_start = jank.start_time + kJankThreshold;
// Bound by |start_time| and |end_time|.
jank_start = std::max(jank_start, start_time);
base::TimeTicks jank_end = std::min(jank.end_time, end_time);
// Find each janky slice, and add it to |janky_slices|.
while (jank_start < jank_end) {
// Convert |jank_start| to a slice label.
int label = (jank_start - start_time) / kJankThreshold;
janky_slices->insert(label);
jank_start += kJankThreshold;
}
}
} // namespace
Calculator::Jank::Jank(base::TimeTicks start_time, base::TimeTicks end_time)
: start_time(start_time), end_time(end_time) {
DCHECK_LE(start_time, end_time);
}
Calculator::Calculator()
: last_calculation_time_(base::TimeTicks::Now()),
most_recent_activity_time_(last_calculation_time_) {}
Calculator::~Calculator() = default;
void Calculator::TaskOrEventFinishedOnUIThread(base::TimeTicks schedule_time,
base::TimeTicks finish_time) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (finish_time - schedule_time >= kJankThreshold) {
GetJanksOnUIThread().emplace_back(schedule_time, finish_time);
}
// We rely on the assumption that |finish_time| is the current time.
CalculateResponsivenessIfNecessary(/*current_time=*/finish_time);
}
void Calculator::TaskOrEventFinishedOnIOThread(base::TimeTicks schedule_time,
base::TimeTicks finish_time) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (finish_time - schedule_time >= kJankThreshold) {
base::AutoLock lock(io_thread_lock_);
GetJanksOnIOThread().emplace_back(schedule_time, finish_time);
}
}
void Calculator::EmitResponsiveness(int janky_slices) {
UMA_HISTOGRAM_COUNTS_1000(
"Browser.Responsiveness.JankyIntervalsPerThirtySeconds", janky_slices);
++emission_count_;
if (emission_count_ == 1) {
// We log this metric every 30 seconds [unless there is no activity on the
// UI and IO threads]. The first time we log this will always cover startup,
// since we're guaranteed to have events on the UI thread during startup.
UMA_HISTOGRAM_COUNTS_1000(
"Browser.Responsiveness.JankyIntervalsPerThirtySeconds.Startup",
janky_slices);
} else {
// Future emissions definitely do not apply to startup.
UMA_HISTOGRAM_COUNTS_1000(
"Browser.Responsiveness.JankyIntervalsPerThirtySeconds.NonStartup",
janky_slices);
}
}
base::TimeTicks Calculator::GetLastCalculationTime() {
return last_calculation_time_;
}
void Calculator::CalculateResponsivenessIfNecessary(
base::TimeTicks current_time) {
base::TimeTicks last_activity_time = most_recent_activity_time_;
most_recent_activity_time_ = current_time;
// We intentionally dump all data if it appears that Chrome was suspended.
// [e.g. machine is asleep, process is backgrounded on Android]. We don't have
// an explicit signal for this. Instead, we rely on the assumption that when
// Chrome is not suspended, there is a steady stream of tasks and events on
// the UI thread. If there's been a significant amount of time since the last
// calculation, then it's likely because Chrome was suspended.
if (current_time - last_activity_time > kSuspendInterval) {
last_calculation_time_ = current_time;
GetJanksOnUIThread().clear();
{
base::AutoLock lock(io_thread_lock_);
GetJanksOnIOThread().clear();
}
return;
}
base::TimeDelta time_since_last_calculation =
current_time - last_calculation_time_;
if (time_since_last_calculation <= kMeasurementInterval)
return;
// At least |kMeasurementInterval| time has passed, so we want to move forward
// |last_calculation_time_| and make measurements based on janks in that
// interval.
int number_of_measurement_intervals =
time_since_last_calculation / kMeasurementInterval;
DCHECK(number_of_measurement_intervals >= 1);
base::TimeTicks new_calculation_time =
last_calculation_time_ +
number_of_measurement_intervals * kMeasurementInterval;
// Acquire the janks in the measurement interval from the UI and IO threads.
std::vector<JankList> janks_from_multiple_threads;
janks_from_multiple_threads.push_back(
TakeJanksOlderThanTime(&GetJanksOnUIThread(), new_calculation_time));
{
base::AutoLock lock(io_thread_lock_);
janks_from_multiple_threads.push_back(
TakeJanksOlderThanTime(&GetJanksOnIOThread(), new_calculation_time));
}
CalculateResponsiveness(std::move(janks_from_multiple_threads),
last_calculation_time_, new_calculation_time);
last_calculation_time_ = new_calculation_time;
}
void Calculator::CalculateResponsiveness(
std::vector<JankList> janks_from_multiple_threads,
base::TimeTicks start_time,
base::TimeTicks end_time) {
while (start_time < end_time) {
base::TimeTicks current_interval_end_time =
start_time + kMeasurementInterval;
// We divide the current measurement interval into slices. Each slice is
// given a monotonically increasing label, from 0 to |kNumberOfSlices - 1|.
// Example [all times in milliseconds since UNIX epoch]:
// The measurement interval is [50135, 80135].
// The slice [50135, 50235] is labeled 0.
// The slice [50235, 50335] is labeled 1.
// ...
// The slice [80035, 80135] is labeled 299.
std::set<int> janky_slices;
for (const JankList& janks : janks_from_multiple_threads) {
for (const Jank& jank : janks) {
AddJankySlices(&janky_slices, jank, start_time,
current_interval_end_time);
}
}
EmitResponsiveness(janky_slices.size());
start_time = current_interval_end_time;
}
}
Calculator::JankList& Calculator::GetJanksOnUIThread() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return janks_on_ui_thread_;
}
Calculator::JankList& Calculator::GetJanksOnIOThread() {
io_thread_lock_.AssertAcquired();
return janks_on_io_thread_;
}
Calculator::JankList Calculator::TakeJanksOlderThanTime(
JankList* janks,
base::TimeTicks end_time) {
// Find all janks with Jank.start_time < |end_time|.
auto it = std::partition(
janks->begin(), janks->end(),
[&end_time](const Jank& jank) { return jank.start_time < end_time; });
// Early exit. We don't need to remove any Janks either, since Jank.end_time
// >= Jank.start_time.
if (it == janks->begin())
return JankList();
JankList janks_to_return(janks->begin(), it);
// Remove all janks with Jank.end_time < |end_time|.
auto first_jank_to_keep = std::partition(
janks->begin(), janks->end(),
[&end_time](const Jank& jank) { return jank.end_time < end_time; });
janks->erase(janks->begin(), first_jank_to_keep);
return janks_to_return;
}
} // namespace responsiveness
} // namespace content