blob: fe4ad43031b1e993ab44b21c7108f0a24971b789 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/latency/janky_duration_tracker.h"
#include <iomanip>
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_path_watcher.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
namespace ui {
namespace {
// Singleton class to Advance() scroll jank duration counts. Watches changes in
// a directory, if specified by commandline flags. When a file is added or
// removed or replaced in the directory, prints the current jank sums to the
// log. Ignores the counts registered before the directory notification is set
// up.
class JankyDurationTracker {
public:
// Pointer to the singleton. Can be called on any thread.
static JankyDurationTracker* Get();
// Advance either the janky count xor the non-janky one. Can be called on any
// thread.
void Advance(bool janky, int count);
private:
friend class base::NoDestructor<JankyDurationTracker>;
JankyDurationTracker();
void InitializeOnThreadPool();
void OnChanged(const base::FilePath& path, bool error);
void ReportTotalsAndReset();
std::atomic<uint64_t> janky_total_ = 0;
std::atomic<uint64_t> non_janky_total_ = 0;
std::atomic<bool> ready_ = false;
std::unique_ptr<base::FilePathWatcher> watcher_;
base::FilePath file_path_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
};
// static
JankyDurationTracker* JankyDurationTracker::Get() {
static base::NoDestructor<JankyDurationTracker> tracker;
return tracker.get();
}
JankyDurationTracker::JankyDurationTracker() {
task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
base::TaskPriority::BEST_EFFORT});
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&JankyDurationTracker::InitializeOnThreadPool,
base::Unretained(this)));
}
void JankyDurationTracker::Advance(bool janky, int count) {
// Avoid counting jank when not requested by the flag. All of the object
// members are initially written before posting the task for initialization,
// which guarantees their consistent visibility from all posted tasks. A
// relaxed load may ignore some early calls to this method without breaking
// consistency.
if (!ready_.load(std::memory_order_relaxed)) {
return;
}
if (janky) {
janky_total_.fetch_add(count, std::memory_order_relaxed);
} else {
non_janky_total_.fetch_add(count, std::memory_order_relaxed);
}
}
void JankyDurationTracker::InitializeOnThreadPool() {
#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_FUCHSIA)
// Neither iOS nor Fuchsia use the base::FilePathWatcher. Avoid the dependency
// on these platforms to save binary size.
return;
#else
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
file_path_ =
command_line->GetSwitchValuePath(switches::kWatchDirForScrollJankReport);
if (file_path_.empty()) {
return;
}
watcher_ = std::make_unique<base::FilePathWatcher>();
if (!watcher_->Watch(file_path_, base::FilePathWatcher::Type::kNonRecursive,
base::BindRepeating(&JankyDurationTracker::OnChanged,
base::Unretained(this)))) {
LOG(ERROR) << "Cannot watch file '" << file_path_.value() << "'";
watcher_.reset();
return;
}
// See the comment in Advance() explaining why the relaxed memory ordering is
// sufficient.
ready_.store(true, std::memory_order_relaxed);
#endif
}
void JankyDurationTracker::OnChanged(const base::FilePath& path, bool error) {
if (!error) {
ReportTotalsAndReset();
}
}
void JankyDurationTracker::ReportTotalsAndReset() {
uint64_t janky_total = janky_total_.exchange(0, std::memory_order_relaxed);
uint64_t non_janky_total =
non_janky_total_.exchange(0, std::memory_order_relaxed);
VLOG(0) << "JankyDurationTrackerCSV:" << janky_total << "," << non_janky_total
<< "," << std::setprecision(2)
<< ((double)janky_total) / (janky_total + non_janky_total);
}
} // namespace
void AdvanceJankyDurationForBenchmarking(bool janky, int count) {
JankyDurationTracker::Get()->Advance(janky, count);
}
} // namespace ui