1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
content / browser / scheduler / responsiveness / jank_monitor_impl.cc [blame]
// Copyright 2019 The Chromium Authors
// 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/jank_monitor_impl.h"
#include "base/compiler_specific.h"
#include "base/observer_list.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "ui/base/ui_base_features.h"
namespace content {
JankMonitor::~JankMonitor() = default;
JankMonitor::Observer::~Observer() = default;
// static
scoped_refptr<JankMonitor> JankMonitor::Create() {
return base::MakeRefCounted<responsiveness::JankMonitorImpl>();
}
namespace responsiveness {
// Interval of the monitor performing jankiness checks against the watched
// threads.
static constexpr int64_t kMonitorCheckIntervalMs = 500;
// A task running for longer than |kJankThresholdMs| is considered janky.
static constexpr int64_t kJankThresholdMs = 1000;
// The threshold (10 sec) for shutting down the monitor timer, in microseconds.
static constexpr int64_t kInactivityThresholdUs =
10 * base::TimeTicks::kMicrosecondsPerSecond;
JankMonitorImpl::JankMonitorImpl()
: timer_(std::make_unique<base::RepeatingTimer>()),
timer_running_(false),
janky_task_id_(nullptr),
last_activity_time_us_(0) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DETACH_FROM_SEQUENCE(monitor_sequence_checker_);
}
JankMonitorImpl::~JankMonitorImpl() = default;
void JankMonitorImpl::AddObserver(content::JankMonitor::Observer* observer) {
base::AutoLock auto_lock(observers_lock_);
observers_.AddObserver(observer);
}
void JankMonitorImpl::RemoveObserver(content::JankMonitor::Observer* observer) {
base::AutoLock auto_lock(observers_lock_);
observers_.RemoveObserver(observer);
}
void JankMonitorImpl::SetUp() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Dependencies in SetUp() and Destroy():
// * Target thread --(may schedule the timer on)--> Monitor thread.
// * Monitor thread --(read/write)--> ThreadExecutionState data members.
// * Target thread --(write)--> ThreadExecutionState data members.
// ThreadExecutionState data members are created first.
ui_thread_exec_state_ = std::make_unique<ThreadExecutionState>();
io_thread_exec_state_ = std::make_unique<ThreadExecutionState>();
// Then the monitor thread.
monitor_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner({});
// Finally set up the MetricSource.
metric_source_ = CreateMetricSource();
metric_source_->SetUp();
}
void JankMonitorImpl::Destroy() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Destroy shuts down the monitor timer and the metric source in parallel.
// |timer_| is shut down and destroyed on the monitor thread. |metric_source_|
// is destroyed in calling its Destroy() method. The shared data members,
// |ui_thread_exec_state_| and |io_thread_exec_state_| is destroyed in the
// JankMonitor dtor, which can happen on either the monitor or the UI thread.
monitor_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&JankMonitorImpl::DestroyOnMonitorThread,
base::RetainedRef(this)));
base::ScopedClosureRunner finish_destroy_metric_source(base::BindOnce(
&JankMonitorImpl::FinishDestroyMetricSource, base::RetainedRef(this)));
metric_source_->Destroy(std::move(finish_destroy_metric_source));
}
void JankMonitorImpl::FinishDestroyMetricSource() {
// Destruction of MetricSource takes place on the UI thread.
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
metric_source_ = nullptr;
}
void JankMonitorImpl::SetUpOnIOThread() {}
void JankMonitorImpl::TearDownOnUIThread() {
// Don't destroy |ui_thread_exec_state_| yet because it might be used if the
// monitor timer runs.
}
void JankMonitorImpl::TearDownOnIOThread() {
// Don't destroy |io_thread_exec_state_| yet because it might be used if the
// monitor timer fires.
}
void JankMonitorImpl::WillRunTaskOnUIThread(
const base::PendingTask* task,
bool /* was_blocked_or_low_priority */) {
DCHECK(ui_thread_exec_state_);
WillRunTaskOrEvent(ui_thread_exec_state_.get(), task);
}
void JankMonitorImpl::DidRunTaskOnUIThread(const base::PendingTask* task) {
DCHECK(ui_thread_exec_state_);
DidRunTaskOrEvent(ui_thread_exec_state_.get(), task);
}
void JankMonitorImpl::WillRunTaskOnIOThread(
const base::PendingTask* task,
bool /* was_blocked_or_low_priority */) {
DCHECK(io_thread_exec_state_);
WillRunTaskOrEvent(io_thread_exec_state_.get(), task);
}
void JankMonitorImpl::DidRunTaskOnIOThread(const base::PendingTask* task) {
DCHECK(io_thread_exec_state_);
DidRunTaskOrEvent(io_thread_exec_state_.get(), task);
}
void JankMonitorImpl::WillRunEventOnUIThread(const void* opaque_identifier) {
DCHECK(ui_thread_exec_state_);
WillRunTaskOrEvent(ui_thread_exec_state_.get(), opaque_identifier);
}
void JankMonitorImpl::DidRunEventOnUIThread(const void* opaque_identifier) {
DCHECK(ui_thread_exec_state_);
DidRunTaskOrEvent(ui_thread_exec_state_.get(), opaque_identifier);
}
void JankMonitorImpl::WillRunTaskOrEvent(
ThreadExecutionState* thread_exec_state,
const void* opaque_identifier) {
thread_exec_state->WillRunTaskOrEvent(opaque_identifier);
if (!timer_running_) {
monitor_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&JankMonitorImpl::StartTimerIfNecessary,
base::RetainedRef(this)));
}
}
void JankMonitorImpl::DidRunTaskOrEvent(ThreadExecutionState* thread_exec_state,
const void* opaque_identifier) {
thread_exec_state->DidRunTaskOrEvent(opaque_identifier);
NotifyJankStopIfNecessary(opaque_identifier);
// This might lead to concurrent writes to |last_activity_time_us_|. Either
// write is fine, and we don't require it to be monotonically increasing.
last_activity_time_us_ =
(base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds();
}
void JankMonitorImpl::StartTimerIfNecessary() {
DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);
// |timer_| is already destroyed. This function is posted from UI or IO thread
// after Destroy() is called. Just do nothing.
if (!timer_)
return;
DCHECK_EQ(timer_->IsRunning(), timer_running_);
// Already running. Maybe both UI and IO threads saw the timer stopped, and
// one attempt has already succeeded.
if (timer_->IsRunning())
return;
static base::TimeDelta monitor_check_interval =
base::Milliseconds(kMonitorCheckIntervalMs);
// RepeatingClosure bound to the timer doesn't hold a ref to |this| because
// the ref will only be released on timer destruction.
timer_->Start(FROM_HERE, monitor_check_interval,
base::BindRepeating(&JankMonitorImpl::OnCheckJankiness,
base::Unretained(this)));
timer_running_ = true;
}
void JankMonitorImpl::StopTimerIfIdle() {
DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);
DCHECK(timer_->IsRunning());
auto now_us = (base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds();
if (now_us - last_activity_time_us_ < kInactivityThresholdUs)
return;
timer_->Stop();
timer_running_ = false;
}
std::unique_ptr<MetricSource> JankMonitorImpl::CreateMetricSource() {
return std::make_unique<MetricSource>(this);
}
void JankMonitorImpl::DestroyOnMonitorThread() {
DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);
DCHECK(timer_);
timer_->Stop();
timer_ = nullptr;
timer_running_ = false;
}
bool JankMonitorImpl::timer_running() const {
return timer_running_;
}
void JankMonitorImpl::OnCheckJankiness() {
DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);
if (janky_task_id_) {
return;
}
auto task_id = ui_thread_exec_state_->CheckJankiness();
if (task_id.has_value()) {
OnJankStarted(*task_id);
return;
}
DCHECK(!janky_task_id_);
// Jankiness is checked in the order of UI, IO thread.
task_id = io_thread_exec_state_->CheckJankiness();
if (task_id.has_value()) {
OnJankStarted(*task_id);
return;
}
DCHECK(!janky_task_id_);
StopTimerIfIdle();
}
void JankMonitorImpl::OnJankStarted(const void* opaque_identifier) {
DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);
janky_task_id_ = opaque_identifier;
base::AutoLock auto_lock(observers_lock_);
for (content::JankMonitor::Observer& observer : observers_)
observer.OnJankStarted();
}
void JankMonitorImpl::OnJankStopped(
MayBeDangling<const void> opaque_identifier) {
DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);
DCHECK_NE(opaque_identifier, nullptr);
if (janky_task_id_ != opaque_identifier)
return;
janky_task_id_ = nullptr;
base::AutoLock auto_lock(observers_lock_);
for (content::JankMonitor::Observer& observer : observers_)
observer.OnJankStopped();
}
void JankMonitorImpl::NotifyJankStopIfNecessary(const void* opaque_identifier) {
if (!janky_task_id_ || janky_task_id_ != opaque_identifier) [[likely]] {
// Most tasks are unlikely to be janky.
return;
}
monitor_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&JankMonitorImpl::OnJankStopped, base::RetainedRef(this),
// It is relatively safe to have `UnsafeDangling` here
// because the ptr is only used as an identifier, and since
// the events should be coming in order, it is unlikely
// that we encounter issue with memory being reused.
base::UnsafeDangling(opaque_identifier)));
}
JankMonitorImpl::ThreadExecutionState::TaskMetadata::~TaskMetadata() = default;
JankMonitorImpl::ThreadExecutionState::ThreadExecutionState() {
// Constructor is always on the UI thread. Detach |target_sequence_checker_|
// to make it work on IO thread.
DETACH_FROM_SEQUENCE(target_sequence_checker_);
DETACH_FROM_SEQUENCE(monitor_sequence_checker_);
}
JankMonitorImpl::ThreadExecutionState::~ThreadExecutionState() = default;
std::optional<const void*>
JankMonitorImpl::ThreadExecutionState::CheckJankiness() {
DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_checker_);
base::TimeTicks now = base::TimeTicks::Now();
static base::TimeDelta jank_threshold = base::Milliseconds(kJankThresholdMs);
base::AutoLock lock(lock_);
if (task_execution_metadata_.empty() ||
(now - task_execution_metadata_.back().execution_start_time) <
jank_threshold) [[likely]] {
// Most tasks are unlikely to be janky.
return std::nullopt;
}
// Mark that the target thread is janky and notify the monitor thread.
return task_execution_metadata_.back().identifier;
}
void JankMonitorImpl::ThreadExecutionState::WillRunTaskOrEvent(
const void* opaque_identifier) {
AssertOnTargetThread();
base::TimeTicks now = base::TimeTicks::Now();
base::AutoLock lock(lock_);
task_execution_metadata_.emplace_back(now, opaque_identifier);
}
void JankMonitorImpl::ThreadExecutionState::DidRunTaskOrEvent(
const void* opaque_identifier) {
AssertOnTargetThread();
base::AutoLock lock(lock_);
if (task_execution_metadata_.empty() ||
opaque_identifier != task_execution_metadata_.back().identifier)
[[unlikely]] {
// Mismatches can happen (e.g: on ozone/wayland when Paste button is pressed
// in context menus, among others). Simply ignore the mismatches for now.
// See https://crbug.com/929813 for the details of why the mismatch
// happens.
#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) && \
BUILDFLAG(IS_OZONE)
task_execution_metadata_.clear();
#endif
return;
}
task_execution_metadata_.pop_back();
}
void JankMonitorImpl::ThreadExecutionState::AssertOnTargetThread() {
DCHECK_CALLED_ON_VALID_SEQUENCE(target_sequence_checker_);
}
} // namespace responsiveness.
} // namespace content.