| // Copyright 2021 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 "cc/metrics/jank_injector.h" |
| |
| #include <map> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/debug/alias.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/string_split.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cc/base/features.h" |
| #include "url/gurl.h" |
| |
| namespace cc { |
| |
| namespace { |
| |
| const char kJankInjectionAllowedURLs[] = "allowed_urls"; |
| const char kJankInjectionClusterSize[] = "cluster"; |
| const char kJankInjectionTargetPercent[] = "percent"; |
| |
| struct JankInjectionParams { |
| JankInjectionParams() = default; |
| ~JankInjectionParams() = default; |
| |
| JankInjectionParams(JankInjectionParams&&) = default; |
| JankInjectionParams& operator=(JankInjectionParams&&) = default; |
| |
| JankInjectionParams(const JankInjectionParams&) = delete; |
| JankInjectionParams& operator=(const JankInjectionParams&) = delete; |
| |
| // The jank injection code blocks the main thread for |jank_duration| amount |
| // of time. |
| base::TimeDelta jank_duration; |
| |
| // When |busy_loop| is set, blocks the main thread in a busy loop for |
| // |jank_duration|. Otherwise, sleeps for |jank_duration|. |
| bool busy_loop = true; |
| }; |
| |
| bool g_jank_enabled_for_test = false; |
| |
| bool IsJankInjectionEnabled() { |
| static bool enabled = |
| base::FeatureList::IsEnabled(features::kJankInjectionAblationFeature); |
| return enabled || g_jank_enabled_for_test; |
| } |
| |
| using AllowedURLsMap = std::map<std::string, std::vector<std::string>>; |
| // Returns a map of <host, <list of paths>> pairs. |
| AllowedURLsMap GetAllowedURLs() { |
| DCHECK(IsJankInjectionEnabled()); |
| AllowedURLsMap urls; |
| std::string url_list = base::GetFieldTrialParamValueByFeature( |
| features::kJankInjectionAblationFeature, kJankInjectionAllowedURLs); |
| for (auto& it : base::SplitString(url_list, ",", base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_ALL)) { |
| GURL url = GURL(it); |
| urls[url.host()].emplace_back(url.path()); |
| } |
| return urls; |
| } |
| |
| bool IsJankInjectionEnabledForURL(const GURL& url) { |
| DCHECK(IsJankInjectionEnabled()); |
| static base::NoDestructor<AllowedURLsMap> allowed_urls(GetAllowedURLs()); |
| if (allowed_urls->empty()) |
| return false; |
| |
| const auto iter = allowed_urls->find(url.host()); |
| if (iter == allowed_urls->end()) |
| return false; |
| |
| const auto& paths = iter->second; |
| const auto& path = url.path_piece(); |
| return paths.end() != |
| std::find_if(paths.begin(), paths.end(), [path](const std::string& p) { |
| return base::StartsWith(path, p); |
| }); |
| } |
| |
| void RunJank(JankInjectionParams params) { |
| TRACE_EVENT0("cc,benchmark", "Injected Jank"); |
| if (params.busy_loop) { |
| // Do some useless work, and prevent any weird compiler optimization from |
| // doing anything here. |
| base::TimeTicks start = base::TimeTicks::Now(); |
| std::vector<base::TimeTicks> dummy; |
| while (base::TimeTicks::Now() - start < params.jank_duration) { |
| dummy.push_back(base::TimeTicks::Now()); |
| if (dummy.size() > 100) { |
| dummy.erase(dummy.begin()); |
| } |
| } |
| base::debug::Alias(&dummy); |
| } else { |
| base::PlatformThread::Sleep(params.jank_duration); |
| } |
| } |
| |
| } // namespace |
| |
| ScopedJankInjectionEnabler::ScopedJankInjectionEnabler() { |
| DCHECK(!g_jank_enabled_for_test); |
| g_jank_enabled_for_test = true; |
| } |
| |
| ScopedJankInjectionEnabler::~ScopedJankInjectionEnabler() { |
| DCHECK(g_jank_enabled_for_test); |
| g_jank_enabled_for_test = false; |
| } |
| |
| JankInjector::JankInjector() { |
| if (IsJankInjectionEnabled()) { |
| config_.target_dropped_frames_percent = |
| base::GetFieldTrialParamByFeatureAsInt( |
| features::kJankInjectionAblationFeature, |
| kJankInjectionTargetPercent, config_.target_dropped_frames_percent); |
| config_.dropped_frame_cluster_size = base::GetFieldTrialParamByFeatureAsInt( |
| features::kJankInjectionAblationFeature, kJankInjectionClusterSize, |
| config_.dropped_frame_cluster_size); |
| } |
| } |
| |
| JankInjector::~JankInjector() = default; |
| |
| bool JankInjector::IsEnabled(const GURL& url) { |
| return IsJankInjectionEnabled() && IsJankInjectionEnabledForURL(url); |
| } |
| |
| void JankInjector::ScheduleJankIfNeeded( |
| const viz::BeginFrameArgs& args, |
| base::SingleThreadTaskRunner* task_runner) { |
| if (ShouldJankCurrentFrame(args)) { |
| ScheduleJank(args, task_runner); |
| did_jank_last_time_ = true; |
| } else { |
| ++total_frames_; |
| did_jank_last_time_ = false; |
| } |
| } |
| |
| bool JankInjector::ShouldJankCurrentFrame( |
| const viz::BeginFrameArgs& args) const { |
| // If jank was injected during the previous frame, then do not inject jank |
| // again now. |
| if (did_jank_last_time_) |
| return false; |
| |
| // Do not jank during the first frame. |
| if (!total_frames_) |
| return false; |
| |
| auto current_jank = janked_frames_ * 100 / total_frames_; |
| // Do not drop any more frames if the injected jank is already above or at the |
| // target. |
| if (current_jank >= config_.target_dropped_frames_percent) |
| return false; |
| |
| // If janking now makes the dropped the frames goes beyond the target, then do |
| // not inject the jank yet. |
| auto next_jank = (janked_frames_ + config_.dropped_frame_cluster_size) * 100 / |
| (total_frames_ + config_.dropped_frame_cluster_size); |
| if (next_jank > config_.target_dropped_frames_percent) |
| return false; |
| |
| return true; |
| } |
| |
| void JankInjector::ScheduleJank(const viz::BeginFrameArgs& args, |
| base::SingleThreadTaskRunner* task_runner) { |
| JankInjectionParams params; |
| params.jank_duration = config_.dropped_frame_cluster_size * args.interval; |
| params.busy_loop = true; |
| task_runner->PostTask(FROM_HERE, base::BindOnce(&RunJank, std::move(params))); |
| |
| janked_frames_ += config_.dropped_frame_cluster_size; |
| total_frames_ += config_.dropped_frame_cluster_size; |
| } |
| |
| } // namespace cc |