// Copyright 2020 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_THREADING_SCOPED_BLOCKING_CALL_INTERNAL_H_ #define BASE_THREADING_SCOPED_BLOCKING_CALL_INTERNAL_H_ #include #include "base/auto_reset.h" #include "base/base_export.h" #include "base/functional/callback_forward.h" #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.h" #include "base/synchronization/lock.h" #include "base/thread_annotations.h" #include "base/time/time.h" #include "base/types/strong_alias.h" namespace base { // Forward-declare types from scoped_blocking_call.h to break cyclic dependency. enum class BlockingType; using IOJankReportingCallback = RepeatingCallback; using OnlyObservedThreadsForTest = StrongAlias; void BASE_EXPORT EnableIOJankMonitoringForProcess(IOJankReportingCallback, OnlyObservedThreadsForTest); // Implementation details of types in scoped_blocking_call.h and classes for a // few key //base types to observe and react to blocking calls. namespace internal { // Interface for an observer to be informed when a thread enters or exits // the scope of ScopedBlockingCall objects. class BASE_EXPORT BlockingObserver { public: virtual ~BlockingObserver() = default; // Invoked when a ScopedBlockingCall is instantiated on the observed thread // where there wasn't an existing ScopedBlockingCall. virtual void BlockingStarted(BlockingType blocking_type) = 0; // Invoked when a WILL_BLOCK ScopedBlockingCall is instantiated on the // observed thread where there was a MAY_BLOCK ScopedBlockingCall but not a // WILL_BLOCK ScopedBlockingCall. virtual void BlockingTypeUpgraded() = 0; // Invoked when the last ScopedBlockingCall on the observed thread is // destroyed. virtual void BlockingEnded() = 0; }; // Registers |new_blocking_observer| on the current thread. It is invalid to // call this on a thread where there is an active ScopedBlockingCall. BASE_EXPORT void SetBlockingObserverForCurrentThread( BlockingObserver* new_blocking_observer); BASE_EXPORT void ClearBlockingObserverForCurrentThread(); // An IOJankMonitoringWindow instruments 1-minute of runtime. Any I/O jank > 1 // second happening during that period will be reported to it. It will then // report via the IOJankReportingCallback in |reporting_callback_storage()| if // it's non-null. https://bit.ly/chrome-io-jank-metric. class BASE_EXPORT [[maybe_unused, nodiscard]] IOJankMonitoringWindow : public RefCountedThreadSafe { public: explicit IOJankMonitoringWindow(TimeTicks start_time); IOJankMonitoringWindow(const IOJankMonitoringWindow&) = delete; IOJankMonitoringWindow& operator=(const IOJankMonitoringWindow&) = delete; // Cancels monitoring and clears this class' static state. static void CancelMonitoringForTesting(); class [[maybe_unused, nodiscard]] ScopedMonitoredCall { public: // Stores a ref to the current IOJankMonitoringWindow if monitoring is // active, keeping it alive at least until the monitored call completes or // Cancel() is invoked. ScopedMonitoredCall(); // Reports to |assigned_jank_window_| if it's non-null. ~ScopedMonitoredCall(); ScopedMonitoredCall(const ScopedMonitoredCall&) = delete; ScopedMonitoredCall& operator=(const ScopedMonitoredCall&) = delete; // Cancels monitoring of this call. void Cancel(); private: TimeTicks call_start_; scoped_refptr assigned_jank_window_; }; static constexpr TimeDelta kIOJankInterval = Seconds(1); static constexpr TimeDelta kMonitoringWindow = Minutes(1); static constexpr TimeDelta kTimeDiscrepancyTimeout = kIOJankInterval * 10; static constexpr int kNumIntervals = kMonitoringWindow / kIOJankInterval; // kIOJankIntervals must integrally fill kMonitoringWindow static_assert((kMonitoringWindow % kIOJankInterval).is_zero(), ""); // Cancelation is simple because it can only affect the current window. static_assert(kTimeDiscrepancyTimeout < kMonitoringWindow, ""); private: friend class base::RefCountedThreadSafe; friend void base::EnableIOJankMonitoringForProcess( IOJankReportingCallback, OnlyObservedThreadsForTest); // No-op if reporting_callback_storage() is null (i.e. unless // EnableIOJankMonitoringForProcess() was called). // When reporting_callback_storage() is non-null : Ensures that there's an // active IOJankMonitoringWindow for Now(), connects it via |next_| to the // previous IOJankMonitoringWindow to let ScopedMonitoredCalls that span // multiple windows report to each window they cover. In the event that Now() // is farther ahead than expected (> 10s), the previous window is |canceled_| // as it was likely interrupted by a system sleep and a new // IOJankMonitoringWindow chain is started from Now(). In all cases, returns a // live reference to the current (old or new) IOJankMonitoringWindow as a // helper so callers that need it don't need to re-acquire // current_jank_window_lock() after calling this. // |recent_now| is a recent sampling of TimeTicks::Now(), avoids // double-sampling Now() from most callers. static scoped_refptr MonitorNextJankWindowIfNecessary( TimeTicks recent_now); // An IOJankMonitoringWindow is destroyed when all refs to it are gone, i.e.: // 1) The window it covers has elapsed and MonitorNextJankWindowIfNecessary() // has replaced it. // 2) All pending ScopedMonitoredCall's in their range have completed // (including the ones that transitively have it in their |next_| chain). ~IOJankMonitoringWindow(); // Called from ~ScopedMonitoredCall(). void OnBlockingCallCompleted(TimeTicks call_start, TimeTicks call_end); // Helper for OnBlockingCallCompleted(). Records |num_janky_intervals| // starting at |local_jank_start_index|. Having this logic separately helps // sane management of |intervals_lock_| when recursive calls through |next_| // pointers are necessary. void AddJank(int local_jank_start_index, int num_janky_intervals); static Lock& current_jank_window_lock(); static scoped_refptr& current_jank_window_storage() EXCLUSIVE_LOCKS_REQUIRED(current_jank_window_lock()); // Storage for callback used to report monitoring results. // NullCallback if monitoring was not enabled for this process. static IOJankReportingCallback& reporting_callback_storage() EXCLUSIVE_LOCKS_REQUIRED(current_jank_window_lock()); Lock intervals_lock_; size_t intervals_jank_count_[kNumIntervals] GUARDED_BY(intervals_lock_) = {}; const TimeTicks start_time_; // Set only once per window, in MonitorNextJankWindowIfNecessary(). Any read // of this value must be ordered after that call in memory and in time. scoped_refptr next_; // Set to true if ~IOJankMonitoringWindow() shouldn't record metrics. // Modifications of this variable must be synchronized with each other and // happen-before ~IOJankMonitoringWindow(). bool canceled_ = false; }; // Common implementation class for both ScopedBlockingCall and // ScopedBlockingCallWithBaseSyncPrimitives without assertions. class BASE_EXPORT [[maybe_unused, nodiscard]] UncheckedScopedBlockingCall { public: enum class BlockingCallType { kRegular, kBaseSyncPrimitives, }; UncheckedScopedBlockingCall(BlockingType blocking_type, BlockingCallType blocking_call_type); UncheckedScopedBlockingCall(const UncheckedScopedBlockingCall&) = delete; UncheckedScopedBlockingCall& operator=(const UncheckedScopedBlockingCall&) = delete; ~UncheckedScopedBlockingCall(); private: const raw_ptr blocking_observer_; // Previous ScopedBlockingCall instantiated on this thread. const raw_ptr previous_scoped_blocking_call_; const base::AutoReset resetter_; // Whether the BlockingType of the current thread was WILL_BLOCK after this // ScopedBlockingCall was instantiated. const bool is_will_block_; // Non-nullopt for non-nested blocking calls of type MAY_BLOCK on foreground // threads which we monitor for I/O jank. std::optional monitored_call_; }; } // namespace internal } // namespace base #endif // BASE_THREADING_SCOPED_BLOCKING_CALL_INTERNAL_H_