1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "cc/metrics/jank_injector.h"
12 #include "base/bind.h"
13 #include "base/debug/alias.h"
14 #include "base/feature_list.h"
15 #include "base/metrics/field_trial_params.h"
16 #include "base/no_destructor.h"
17 #include "base/ranges/algorithm.h"
18 #include "base/strings/string_split.h"
19 #include "base/time/time.h"
20 #include "base/trace_event/trace_event.h"
21 #include "cc/base/features.h"
28 constexpr char kTraceCategory[] =
29 "cc,benchmark," TRACE_DISABLED_BY_DEFAULT("devtools.timeline.frame");
31 const char kJankInjectionAllowedURLs[] = "allowed_urls";
32 const char kJankInjectionClusterSize[] = "cluster";
33 const char kJankInjectionTargetPercent[] = "percent";
35 struct JankInjectionParams {
36 JankInjectionParams() = default;
37 ~JankInjectionParams() = default;
39 JankInjectionParams(JankInjectionParams&&) = default;
40 JankInjectionParams& operator=(JankInjectionParams&&) = default;
42 JankInjectionParams(const JankInjectionParams&) = delete;
43 JankInjectionParams& operator=(const JankInjectionParams&) = delete;
45 // The jank injection code blocks the main thread for |jank_duration| amount
47 base::TimeDelta jank_duration;
49 // When |busy_loop| is set, blocks the main thread in a busy loop for
50 // |jank_duration|. Otherwise, sleeps for |jank_duration|.
51 bool busy_loop = true;
54 bool g_jank_enabled_for_test = false;
56 bool IsJankInjectionEnabled() {
58 base::FeatureList::IsEnabled(features::kJankInjectionAblationFeature);
59 return enabled || g_jank_enabled_for_test;
62 using AllowedURLsMap = std::map<std::string, std::vector<std::string>>;
63 // Returns a map of <host, <list of paths>> pairs.
64 AllowedURLsMap GetAllowedURLs() {
65 DCHECK(IsJankInjectionEnabled());
67 std::string url_list = base::GetFieldTrialParamValueByFeature(
68 features::kJankInjectionAblationFeature, kJankInjectionAllowedURLs);
69 for (auto& it : base::SplitString(url_list, ",", base::TRIM_WHITESPACE,
70 base::SPLIT_WANT_ALL)) {
72 urls[url.host()].emplace_back(url.path());
77 bool IsJankInjectionEnabledForURL(const GURL& url) {
78 DCHECK(IsJankInjectionEnabled());
79 static base::NoDestructor<AllowedURLsMap> allowed_urls(GetAllowedURLs());
80 if (allowed_urls->empty())
83 const auto iter = allowed_urls->find(url.host());
84 if (iter == allowed_urls->end())
87 const auto& paths = iter->second;
88 const auto& path = url.path_piece();
89 return base::ranges::any_of(paths, [path](const std::string& p) {
90 return base::StartsWith(path, p);
94 void RunJank(JankInjectionParams params) {
95 TRACE_EVENT0(kTraceCategory, "Injected Jank");
96 if (params.busy_loop) {
97 // Do some useless work, and prevent any weird compiler optimization from
98 // doing anything here.
99 base::TimeTicks start = base::TimeTicks::Now();
100 std::vector<base::TimeTicks> dummy;
101 while (base::TimeTicks::Now() - start < params.jank_duration) {
102 dummy.push_back(base::TimeTicks::Now());
103 if (dummy.size() > 100) {
104 dummy.erase(dummy.begin());
107 base::debug::Alias(&dummy);
109 base::PlatformThread::Sleep(params.jank_duration);
115 ScopedJankInjectionEnabler::ScopedJankInjectionEnabler() {
116 DCHECK(!g_jank_enabled_for_test);
117 g_jank_enabled_for_test = true;
120 ScopedJankInjectionEnabler::~ScopedJankInjectionEnabler() {
121 DCHECK(g_jank_enabled_for_test);
122 g_jank_enabled_for_test = false;
125 JankInjector::JankInjector() {
126 if (IsJankInjectionEnabled()) {
127 config_.target_dropped_frames_percent =
128 base::GetFieldTrialParamByFeatureAsInt(
129 features::kJankInjectionAblationFeature,
130 kJankInjectionTargetPercent, config_.target_dropped_frames_percent);
131 config_.dropped_frame_cluster_size = base::GetFieldTrialParamByFeatureAsInt(
132 features::kJankInjectionAblationFeature, kJankInjectionClusterSize,
133 config_.dropped_frame_cluster_size);
137 JankInjector::~JankInjector() = default;
139 bool JankInjector::IsEnabled(const GURL& url) {
140 return IsJankInjectionEnabled() && IsJankInjectionEnabledForURL(url);
143 void JankInjector::ScheduleJankIfNeeded(
144 const viz::BeginFrameArgs& args,
145 base::SingleThreadTaskRunner* task_runner) {
146 if (ShouldJankCurrentFrame(args)) {
147 ScheduleJank(args, task_runner);
148 did_jank_last_time_ = true;
151 did_jank_last_time_ = false;
155 bool JankInjector::ShouldJankCurrentFrame(
156 const viz::BeginFrameArgs& args) const {
157 // If jank was injected during the previous frame, then do not inject jank
159 if (did_jank_last_time_)
162 // Do not jank during the first frame.
166 auto current_jank = janked_frames_ * 100 / total_frames_;
167 // Do not drop any more frames if the injected jank is already above or at the
169 if (current_jank >= config_.target_dropped_frames_percent)
172 // If janking now makes the dropped the frames goes beyond the target, then do
173 // not inject the jank yet.
174 auto next_jank = (janked_frames_ + config_.dropped_frame_cluster_size) * 100 /
175 (total_frames_ + config_.dropped_frame_cluster_size);
176 if (next_jank > config_.target_dropped_frames_percent)
182 void JankInjector::ScheduleJank(const viz::BeginFrameArgs& args,
183 base::SingleThreadTaskRunner* task_runner) {
184 JankInjectionParams params;
185 params.jank_duration = config_.dropped_frame_cluster_size * args.interval;
186 params.busy_loop = true;
187 task_runner->PostTask(FROM_HERE, base::BindOnce(&RunJank, std::move(params)));
189 janked_frames_ += config_.dropped_frame_cluster_size;
190 total_frames_ += config_.dropped_frame_cluster_size;