[M108 Migration][VD] Avoid pending frame counter becoming negative
[platform/framework/web/chromium-efl.git] / cc / metrics / jank_injector.cc
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.
4
5 #include "cc/metrics/jank_injector.h"
6
7 #include <map>
8 #include <string>
9 #include <utility>
10 #include <vector>
11
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"
22 #include "url/gurl.h"
23
24 namespace cc {
25
26 namespace {
27
28 constexpr char kTraceCategory[] =
29     "cc,benchmark," TRACE_DISABLED_BY_DEFAULT("devtools.timeline.frame");
30
31 const char kJankInjectionAllowedURLs[] = "allowed_urls";
32 const char kJankInjectionClusterSize[] = "cluster";
33 const char kJankInjectionTargetPercent[] = "percent";
34
35 struct JankInjectionParams {
36   JankInjectionParams() = default;
37   ~JankInjectionParams() = default;
38
39   JankInjectionParams(JankInjectionParams&&) = default;
40   JankInjectionParams& operator=(JankInjectionParams&&) = default;
41
42   JankInjectionParams(const JankInjectionParams&) = delete;
43   JankInjectionParams& operator=(const JankInjectionParams&) = delete;
44
45   // The jank injection code blocks the main thread for |jank_duration| amount
46   // of time.
47   base::TimeDelta jank_duration;
48
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;
52 };
53
54 bool g_jank_enabled_for_test = false;
55
56 bool IsJankInjectionEnabled() {
57   static bool enabled =
58       base::FeatureList::IsEnabled(features::kJankInjectionAblationFeature);
59   return enabled || g_jank_enabled_for_test;
60 }
61
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());
66   AllowedURLsMap urls;
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)) {
71     GURL url = GURL(it);
72     urls[url.host()].emplace_back(url.path());
73   }
74   return urls;
75 }
76
77 bool IsJankInjectionEnabledForURL(const GURL& url) {
78   DCHECK(IsJankInjectionEnabled());
79   static base::NoDestructor<AllowedURLsMap> allowed_urls(GetAllowedURLs());
80   if (allowed_urls->empty())
81     return false;
82
83   const auto iter = allowed_urls->find(url.host());
84   if (iter == allowed_urls->end())
85     return false;
86
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);
91   });
92 }
93
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());
105       }
106     }
107     base::debug::Alias(&dummy);
108   } else {
109     base::PlatformThread::Sleep(params.jank_duration);
110   }
111 }
112
113 }  // namespace
114
115 ScopedJankInjectionEnabler::ScopedJankInjectionEnabler() {
116   DCHECK(!g_jank_enabled_for_test);
117   g_jank_enabled_for_test = true;
118 }
119
120 ScopedJankInjectionEnabler::~ScopedJankInjectionEnabler() {
121   DCHECK(g_jank_enabled_for_test);
122   g_jank_enabled_for_test = false;
123 }
124
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);
134   }
135 }
136
137 JankInjector::~JankInjector() = default;
138
139 bool JankInjector::IsEnabled(const GURL& url) {
140   return IsJankInjectionEnabled() && IsJankInjectionEnabledForURL(url);
141 }
142
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;
149   } else {
150     ++total_frames_;
151     did_jank_last_time_ = false;
152   }
153 }
154
155 bool JankInjector::ShouldJankCurrentFrame(
156     const viz::BeginFrameArgs& args) const {
157   // If jank was injected during the previous frame, then do not inject jank
158   // again now.
159   if (did_jank_last_time_)
160     return false;
161
162   // Do not jank during the first frame.
163   if (!total_frames_)
164     return false;
165
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
168   // target.
169   if (current_jank >= config_.target_dropped_frames_percent)
170     return false;
171
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)
177     return false;
178
179   return true;
180 }
181
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)));
188
189   janked_frames_ += config_.dropped_frame_cluster_size;
190   total_frames_ += config_.dropped_frame_cluster_size;
191 }
192
193 }  // namespace cc