// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/synchronization/lock_metrics_recorder.h"

#include "base/auto_reset.h"
#include "base/check.h"
#include "base/containers/ring_buffer.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/strings/strcat.h"
#include "base/threading/platform_thread.h"
#include "base/threading/platform_thread_ref.h"
#include "base/time/time.h"

namespace base {

namespace {

void ReportHistogram(const TimeDelta& sample) {
  // The histogram bounds (1us to 1s) are chosen to select for meaningful lock
  // contention. A lower bound of 1us (the approximate maximum overhead of a
  // no-op syscall) helps filter out noise from uncontended lock acquisitions
  // including cases where the detection of contention is best-effort, such as
  // in Lock. An upper bound of 1s is used because waits longer than this
  // are exceptionally rare, and bucketing them together is sufficient for
  // analysis.
  static const NoDestructor<const std::string> base_lock_hist_name(
      StrCat({"Scheduling.ContendedLockAcquisitionTime.BaseLock.",
              PlatformThread::GetName()}));
  UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(*base_lock_hist_name, sample,
                                          Microseconds(1), Seconds(1), 100);
}

}  // namespace

void LockMetricsRecorder::RecordLockAcquisitionTime(TimeDelta sample) {
  buffer_.SaveToBuffer(sample);
}

void LockMetricsRecorder::ForEachSample(FunctionRef<void(const TimeDelta&)> f) {
  CHECK(!iterating_in_progress_);
  // Set the `reporting_in_progress_` flag to true to prevent re-entrancy due to
  // any lock contention during the recording of the histogram. This keeps the
  // recording and reporting logic simple at the cost of a tiny blind-spot in
  // our metrics.
  AutoReset<bool> mark_iterating_in_progress(&iterating_in_progress_, true);
  for (auto it = buffer_.Begin(); it; ++it) {
    f(**it);
  }
  buffer_.Clear();
}

void LockMetricsRecorder::ReportLockAcquisitionTimes() {
  // Only report samples from target thread.
  if (!IsCurrentThreadTarget()) {
    return;
  }

  ForEachSample(ReportHistogram);
}

// static
void LockMetricsRecorder::SetTargetCurrentThread() {
  PlatformThreadRef current_thread = PlatformThread::CurrentRef();
  PlatformThreadRef prev_target_thread =
      target_thread_.exchange(current_thread, std::memory_order_relaxed);
  CHECK(prev_target_thread.is_null() || prev_target_thread == current_thread);
}

// static
LockMetricsRecorder* LockMetricsRecorder::Get() {
  static LockMetricsRecorder g_lock_metrics_recorder;
  return &g_lock_metrics_recorder;
}

// static
LockMetricsRecorder::ScopedLockAcquisitionTimer
LockMetricsRecorder::ScopedLockAcquisitionTimer::CreateForTest(
    LockMetricsRecorder* recorder) {
  return LockMetricsRecorder::ScopedLockAcquisitionTimer(recorder);
}

}  // namespace base
