blob: c1bbbb2bdade2ded31477ca3dfd693da7b8c0d73 [file] [log] [blame]
// 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.
#ifndef BASE_SYNCHRONIZATION_LOCK_METRICS_RECORDER_H_
#define BASE_SYNCHRONIZATION_LOCK_METRICS_RECORDER_H_
#include <atomic>
#include <cstddef>
#include <optional>
#include "base/base_export.h"
#include "base/compiler_specific.h"
#include "base/containers/ring_buffer.h"
#include "base/functional/function_ref.h"
#include "base/memory/stack_allocated.h"
#include "base/rand_util.h"
#include "base/time/time.h"
#include "base/time/time_override.h"
namespace base {
// A class used to hold samples of metrics related to locks.
//
// `LockMetricsRecorder` is not thread-safe and all samples must be recorded
// from the same thread for the lifetime of the process after setting that
// thread as the target thread.
class BASE_EXPORT LockMetricsRecorder {
public:
// The internal buffer size is a trade-off between memory usage and the number
// of samples that can be stored. With sampling, this buffer size should be
// sufficient for most cases. If the buffer overflows, the RingBuffer will
// overwrite the oldest samples.
constexpr static size_t kMaxSamples = 256;
LockMetricsRecorder() = default;
LockMetricsRecorder(const LockMetricsRecorder&) = delete;
LockMetricsRecorder& operator=(const LockMetricsRecorder&) = delete;
~LockMetricsRecorder() = default;
// Get the global instance of lock metrics recorder.
static LockMetricsRecorder* Get();
// Sets current thread as the target thread for recording lock-related
// metrics.
//
// This function can be called multiple times from the same thread but will
// crash if called from different threads.
void SetTargetCurrentThread();
inline bool IsCurrentThreadTarget() const {
return target_thread_.load(std::memory_order_relaxed) ==
PlatformThread::CurrentRef();
}
inline bool ShouldRecordLockAcquisitionTime() const {
return IsCurrentThreadTarget() && !iterating_in_progress_ &&
metrics_sub_sampler_.ShouldSample(kSamplingRatio);
}
// The type of lock the sample is associated with.
enum class LockType : size_t {
// For samples associated with base::Lock
kBaseLock = 0,
// For samples associated with partition_alloc::internal::Lock
kPartitionAllocLock = 1,
kMax = kPartitionAllocLock,
};
// Records a sample into the internal buffer. Must be called on the target
// thread.
void RecordLockAcquisitionTime(TimeDelta sample, LockType type);
// Report lock acquisition times to UMA histograms, if the current thread is
// the target thread.
void ReportLockAcquisitionTimes();
// Iterate over all the samples of the given type and synchronously call the
// FunctionRef for each sample. Only exposed for testing. Call
// `ReportLockAcquisitionTimes()` to report histograms for all the stored
// samples.
void ForEachSample(LockType type, FunctionRef<void(const TimeDelta&)> f);
// Timer that records into a lock metrics object.
class BASE_EXPORT ScopedLockAcquisitionTimer {
STACK_ALLOCATED();
public:
ScopedLockAcquisitionTimer()
: ScopedLockAcquisitionTimer(LockMetricsRecorder::Get()) {}
ScopedLockAcquisitionTimer(const ScopedLockAcquisitionTimer&) = delete;
ScopedLockAcquisitionTimer& operator=(const ScopedLockAcquisitionTimer&) =
delete;
~ScopedLockAcquisitionTimer() {
if (!start_time_.has_value()) [[likely]] {
return;
}
lock_metrics_->RecordLockAcquisitionTime(
subtle::TimeTicksNowIgnoringOverride() - *start_time_,
LockType::kBaseLock);
}
static ScopedLockAcquisitionTimer CreateForTest(
LockMetricsRecorder* lock_metrics);
private:
explicit ScopedLockAcquisitionTimer(LockMetricsRecorder* lock_metrics)
: lock_metrics_(lock_metrics) {
if (!lock_metrics_->ShouldRecordLockAcquisitionTime()) [[likely]] {
return;
}
start_time_.emplace(subtle::TimeTicksNowIgnoringOverride());
}
// `ElapsedTimer` is not used here since it is mocked in tests and the mock
// might acquire a base::Lock thereby causing re-entrancy.
std::optional<TimeTicks> start_time_;
// It is safe to hold onto the pointer to the lock metrics recorder since
// it points to a global variable.
const raw_ptr<LockMetricsRecorder> lock_metrics_;
};
private:
constexpr static double kSamplingRatio = 0.001;
std::array<RingBuffer<TimeDelta, kMaxSamples>,
static_cast<size_t>(LockType::kMax) + 1>
buffer_;
MetricsSubSampler metrics_sub_sampler_;
bool iterating_in_progress_ = false;
// Thread local variables on Android are extremely slow. So on the hot-path,
// use atomics to record the target thread-ref and read it back from multiple
// threads, adding the constraints that samples can be recorded only from the
// same thread for the lifetime of the process.
std::atomic<PlatformThreadRef> target_thread_;
};
} // namespace base
#endif // BASE_SYNCHRONIZATION_LOCK_METRICS_RECORDER_H_