Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:27:20

0001 // Copyright 2018 The Abseil Authors.
0002 //
0003 // Licensed under the Apache License, Version 2.0 (the "License");
0004 // you may not use this file except in compliance with the License.
0005 // You may obtain a copy of the License at
0006 //
0007 //      https://www.apache.org/licenses/LICENSE-2.0
0008 //
0009 // Unless required by applicable law or agreed to in writing, software
0010 // distributed under the License is distributed on an "AS IS" BASIS,
0011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0012 // See the License for the specific language governing permissions and
0013 // limitations under the License.
0014 //
0015 // -----------------------------------------------------------------------------
0016 // File: sample_recorder.h
0017 // -----------------------------------------------------------------------------
0018 //
0019 // This header file defines a lock-free linked list for recording samples
0020 // collected from a random/stochastic process.
0021 //
0022 // This utility is internal-only. Use at your own risk.
0023 
0024 #ifndef ABSL_PROFILING_INTERNAL_SAMPLE_RECORDER_H_
0025 #define ABSL_PROFILING_INTERNAL_SAMPLE_RECORDER_H_
0026 
0027 #include <atomic>
0028 #include <cstddef>
0029 #include <functional>
0030 
0031 #include "absl/base/config.h"
0032 #include "absl/base/thread_annotations.h"
0033 #include "absl/synchronization/mutex.h"
0034 #include "absl/time/time.h"
0035 
0036 namespace absl {
0037 ABSL_NAMESPACE_BEGIN
0038 namespace profiling_internal {
0039 
0040 // Sample<T> that has members required for linking samples in the linked list of
0041 // samples maintained by the SampleRecorder.  Type T defines the sampled data.
0042 template <typename T>
0043 struct Sample {
0044   // Guards the ability to restore the sample to a pristine state.  This
0045   // prevents races with sampling and resurrecting an object.
0046   absl::Mutex init_mu;
0047   T* next = nullptr;
0048   T* dead ABSL_GUARDED_BY(init_mu) = nullptr;
0049   int64_t weight;  // How many sampling events were required to sample this one.
0050 };
0051 
0052 // Holds samples and their associated stack traces with a soft limit of
0053 // `SetHashtablezMaxSamples()`.
0054 //
0055 // Thread safe.
0056 template <typename T>
0057 class SampleRecorder {
0058  public:
0059   SampleRecorder();
0060   ~SampleRecorder();
0061 
0062   // Registers for sampling.  Returns an opaque registration info.
0063   template <typename... Targs>
0064   T* Register(Targs&&... args);
0065 
0066   // Unregisters the sample.
0067   void Unregister(T* sample);
0068 
0069   // The dispose callback will be called on all samples the moment they are
0070   // being unregistered. Only affects samples that are unregistered after the
0071   // callback has been set.
0072   // Returns the previous callback.
0073   using DisposeCallback = void (*)(const T&);
0074   DisposeCallback SetDisposeCallback(DisposeCallback f);
0075 
0076   // Iterates over all the registered `StackInfo`s.  Returning the number of
0077   // samples that have been dropped.
0078   int64_t Iterate(const std::function<void(const T& stack)>& f);
0079 
0080   size_t GetMaxSamples() const;
0081   void SetMaxSamples(size_t max);
0082 
0083  private:
0084   void PushNew(T* sample);
0085   void PushDead(T* sample);
0086   template <typename... Targs>
0087   T* PopDead(Targs... args);
0088 
0089   std::atomic<size_t> dropped_samples_;
0090   std::atomic<size_t> size_estimate_;
0091   std::atomic<size_t> max_samples_{1 << 20};
0092 
0093   // Intrusive lock free linked lists for tracking samples.
0094   //
0095   // `all_` records all samples (they are never removed from this list) and is
0096   // terminated with a `nullptr`.
0097   //
0098   // `graveyard_.dead` is a circular linked list.  When it is empty,
0099   // `graveyard_.dead == &graveyard`.  The list is circular so that
0100   // every item on it (even the last) has a non-null dead pointer.  This allows
0101   // `Iterate` to determine if a given sample is live or dead using only
0102   // information on the sample itself.
0103   //
0104   // For example, nodes [A, B, C, D, E] with [A, C, E] alive and [B, D] dead
0105   // looks like this (G is the Graveyard):
0106   //
0107   //           +---+    +---+    +---+    +---+    +---+
0108   //    all -->| A |--->| B |--->| C |--->| D |--->| E |
0109   //           |   |    |   |    |   |    |   |    |   |
0110   //   +---+   |   | +->|   |-+  |   | +->|   |-+  |   |
0111   //   | G |   +---+ |  +---+ |  +---+ |  +---+ |  +---+
0112   //   |   |         |        |        |        |
0113   //   |   | --------+        +--------+        |
0114   //   +---+                                    |
0115   //     ^                                      |
0116   //     +--------------------------------------+
0117   //
0118   std::atomic<T*> all_;
0119   T graveyard_;
0120 
0121   std::atomic<DisposeCallback> dispose_;
0122 };
0123 
0124 template <typename T>
0125 typename SampleRecorder<T>::DisposeCallback
0126 SampleRecorder<T>::SetDisposeCallback(DisposeCallback f) {
0127   return dispose_.exchange(f, std::memory_order_relaxed);
0128 }
0129 
0130 template <typename T>
0131 SampleRecorder<T>::SampleRecorder()
0132     : dropped_samples_(0), size_estimate_(0), all_(nullptr), dispose_(nullptr) {
0133   absl::MutexLock l(&graveyard_.init_mu);
0134   graveyard_.dead = &graveyard_;
0135 }
0136 
0137 template <typename T>
0138 SampleRecorder<T>::~SampleRecorder() {
0139   T* s = all_.load(std::memory_order_acquire);
0140   while (s != nullptr) {
0141     T* next = s->next;
0142     delete s;
0143     s = next;
0144   }
0145 }
0146 
0147 template <typename T>
0148 void SampleRecorder<T>::PushNew(T* sample) {
0149   sample->next = all_.load(std::memory_order_relaxed);
0150   while (!all_.compare_exchange_weak(sample->next, sample,
0151                                      std::memory_order_release,
0152                                      std::memory_order_relaxed)) {
0153   }
0154 }
0155 
0156 template <typename T>
0157 void SampleRecorder<T>::PushDead(T* sample) {
0158   if (auto* dispose = dispose_.load(std::memory_order_relaxed)) {
0159     dispose(*sample);
0160   }
0161 
0162   absl::MutexLock graveyard_lock(&graveyard_.init_mu);
0163   absl::MutexLock sample_lock(&sample->init_mu);
0164   sample->dead = graveyard_.dead;
0165   graveyard_.dead = sample;
0166 }
0167 
0168 template <typename T>
0169 template <typename... Targs>
0170 T* SampleRecorder<T>::PopDead(Targs... args) {
0171   absl::MutexLock graveyard_lock(&graveyard_.init_mu);
0172 
0173   // The list is circular, so eventually it collapses down to
0174   //   graveyard_.dead == &graveyard_
0175   // when it is empty.
0176   T* sample = graveyard_.dead;
0177   if (sample == &graveyard_) return nullptr;
0178 
0179   absl::MutexLock sample_lock(&sample->init_mu);
0180   graveyard_.dead = sample->dead;
0181   sample->dead = nullptr;
0182   sample->PrepareForSampling(std::forward<Targs>(args)...);
0183   return sample;
0184 }
0185 
0186 template <typename T>
0187 template <typename... Targs>
0188 T* SampleRecorder<T>::Register(Targs&&... args) {
0189   size_t size = size_estimate_.fetch_add(1, std::memory_order_relaxed);
0190   if (size > max_samples_.load(std::memory_order_relaxed)) {
0191     size_estimate_.fetch_sub(1, std::memory_order_relaxed);
0192     dropped_samples_.fetch_add(1, std::memory_order_relaxed);
0193     return nullptr;
0194   }
0195 
0196   T* sample = PopDead(args...);
0197   if (sample == nullptr) {
0198     // Resurrection failed.  Hire a new warlock.
0199     sample = new T();
0200     {
0201       absl::MutexLock sample_lock(&sample->init_mu);
0202       // If flag initialization happens to occur (perhaps in another thread)
0203       // while in this block, it will lock `graveyard_` which is usually always
0204       // locked before any sample. This will appear as a lock inversion.
0205       // However, this code is run exactly once per sample, and this sample
0206       // cannot be accessed until after it is returned from this method.  This
0207       // means that this lock state can never be recreated, so we can safely
0208       // inform the deadlock detector to ignore it.
0209       sample->init_mu.ForgetDeadlockInfo();
0210       sample->PrepareForSampling(std::forward<Targs>(args)...);
0211     }
0212     PushNew(sample);
0213   }
0214 
0215   return sample;
0216 }
0217 
0218 template <typename T>
0219 void SampleRecorder<T>::Unregister(T* sample) {
0220   PushDead(sample);
0221   size_estimate_.fetch_sub(1, std::memory_order_relaxed);
0222 }
0223 
0224 template <typename T>
0225 int64_t SampleRecorder<T>::Iterate(
0226     const std::function<void(const T& stack)>& f) {
0227   T* s = all_.load(std::memory_order_acquire);
0228   while (s != nullptr) {
0229     absl::MutexLock l(&s->init_mu);
0230     if (s->dead == nullptr) {
0231       f(*s);
0232     }
0233     s = s->next;
0234   }
0235 
0236   return dropped_samples_.load(std::memory_order_relaxed);
0237 }
0238 
0239 template <typename T>
0240 void SampleRecorder<T>::SetMaxSamples(size_t max) {
0241   max_samples_.store(max, std::memory_order_release);
0242 }
0243 
0244 template <typename T>
0245 size_t SampleRecorder<T>::GetMaxSamples() const {
0246   return max_samples_.load(std::memory_order_acquire);
0247 }
0248 
0249 }  // namespace profiling_internal
0250 ABSL_NAMESPACE_END
0251 }  // namespace absl
0252 
0253 #endif  // ABSL_PROFILING_INTERNAL_SAMPLE_RECORDER_H_