1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209

base / threading / scoped_blocking_call_internal.h [blame]

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

#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<void(int, int)>;
using OnlyObservedThreadsForTest =
    StrongAlias<class OnlyObservedThreadsTag, bool>;
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<IOJankMonitoringWindow> {
 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<IOJankMonitoringWindow> 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<IOJankMonitoringWindow>;
  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<IOJankMonitoringWindow> 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<IOJankMonitoringWindow>& 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<IOJankMonitoringWindow> 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<BlockingObserver> blocking_observer_;

  // Previous ScopedBlockingCall instantiated on this thread.
  const raw_ptr<UncheckedScopedBlockingCall> previous_scoped_blocking_call_;

  const base::AutoReset<UncheckedScopedBlockingCall*> 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<IOJankMonitoringWindow::ScopedMonitoredCall> monitored_call_;
};

}  // namespace internal
}  // namespace base

#endif  // BASE_THREADING_SCOPED_BLOCKING_CALL_INTERNAL_H_