chromium/ui/latency/janky_duration_tracker.cc

// 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