| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #ifndef RUNTIME_VM_HEAP_SAMPLER_H_ |
| #define RUNTIME_VM_HEAP_SAMPLER_H_ |
| |
| #if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER) |
| |
| #include <atomic> |
| |
| #include "include/dart_api.h" |
| #include "vm/allocation.h" |
| #include "vm/globals.h" |
| |
| namespace dart { |
| |
| // Forward declarations. |
| class RwLock; |
| class Thread; |
| |
| // Poisson sampler for memory allocations. We apply sampling individually to |
| // each byte. The whole allocation gets accounted as often as the number of |
| // sampled bytes it contains. |
| // |
| // Heavily inspired by Perfetto's Sampler class: |
| // https://source.chromium.org/chromium/chromium/src/+/531db6ec90bd7194d4d8064588966a0118d1495c:third_party/perfetto/src/profiling/memory/sampler.h;l=34 |
| class HeapProfileSampler { |
| public: |
| explicit HeapProfileSampler(Thread* thread); |
| |
| // Enables or disables heap profiling for all threads. |
| // |
| // NOTE: the enabled state will update on a thread-by-thread basis once |
| // the thread performs an interrupt check. There is no guarantee that the |
| // enabled state will be updated for each thread by the time this method |
| // returns. |
| static void Enable(bool enabled); |
| static bool enabled() { return enabled_; } |
| |
| // Updates the heap profiling sampling interval for all threads. |
| // |
| // NOTE: the sampling interval will update on a thread-by-thread basis once |
| // the thread performs an interrupt check. There is no guarantee that the |
| // sampling interval will be updated for each thread by the time this method |
| // returns. |
| static void SetSamplingInterval(intptr_t bytes_interval); |
| |
| // Updates the callback that's invoked when a sample is collected. |
| static void SetSamplingCallback( |
| Dart_HeapSamplingCreateCallback create_callback, |
| Dart_HeapSamplingDeleteCallback delete_callback); |
| |
| static Dart_HeapSamplingDeleteCallback delete_callback() { |
| return delete_callback_; |
| } |
| |
| void Initialize(); |
| void Cleanup() { |
| ResetState(); |
| last_sample_size_ = kUninitialized; |
| } |
| |
| // Notifies the thread that it needs to update the enabled state of the heap |
| // sampling profiler. |
| // |
| // This method is safe to call from any thread. |
| void ScheduleUpdateThreadEnable(); |
| |
| // Returns true if [ScheduleUpdateThreadEnable()] has been invoked. |
| // |
| // Calling this method will clear the state set by |
| // [ScheduleUpdateThreadEnable()]. |
| // |
| // This method is safe to call from any thread. |
| bool ShouldUpdateThreadEnable() { |
| return schedule_thread_enable_.exchange(false); |
| } |
| |
| // Updates the enabled state of the thread's heap sampling profiler. |
| // |
| // WARNING: This method can only be called by the thread associated with this |
| // profiler instance to avoid concurrent modification of the thread's TLAB. |
| void UpdateThreadEnable(); |
| |
| // Notifies the thread that it needs to update the sampling interval of its |
| // heap sampling profiler. |
| // |
| // This method is safe to call from any thread. |
| void ScheduleSetThreadSamplingInterval(); |
| |
| // Returns true if [ScheduleSetThreadSamplingInterval()] has been invoked. |
| // |
| // Calling this method will clear the state set by |
| // [ScheduleSetThreadEnable()]. |
| // |
| // This method is safe to call from any thread. |
| bool ShouldSetThreadSamplingInterval() { |
| return schedule_thread_set_sampling_interval_.exchange(false); |
| } |
| |
| // Updates the sampling interval of the thread's heap sampling profiler. |
| // |
| // WARNING: This method can only be called by the thread associated with this |
| // profiler instance to avoid concurrent modification of the thread's TLAB. |
| void SetThreadSamplingInterval(); |
| |
| // Updates internal book keeping tracking the remaining size of the sampling |
| // interval. This method must be called when a TLAB is torn down to ensure |
| // that a future TLAB is initialized with the correct sampling interval. |
| void HandleReleasedTLAB(Thread* thread); |
| |
| // Handles the creation of a new TLAB by updating its boundaries based on the |
| // remaining sampling interval. |
| // |
| // is_first_tlab should be set to true if this is the first TLAB associated |
| // with thread_ in order to correctly set the TLAB boundaries to match the |
| // remaining sampling interval that's been used to keep track of old space |
| // allocations. |
| void HandleNewTLAB(intptr_t old_tlab_remaining_space, bool is_first_tlab); |
| |
| void* InvokeCallbackForLastSample(intptr_t cid); |
| |
| bool HasOutstandingSample() const { |
| return last_sample_size_ != kUninitialized; |
| } |
| |
| void SampleNewSpaceAllocation(intptr_t allocation_size); |
| void SampleOldSpaceAllocation(intptr_t allocation_size); |
| |
| private: |
| void ResetState(); |
| void ResetIntervalState() { interval_to_next_sample_ = kUninitialized; } |
| |
| void UpdateThreadEnableLocked(); |
| |
| void SetThreadSamplingIntervalLocked(); |
| void SetNextSamplingIntervalLocked(intptr_t next_interval); |
| |
| intptr_t GetNextSamplingIntervalLocked(); |
| intptr_t NumberOfSamplesLocked(intptr_t allocation_size); |
| |
| // Helper to calculate the remaining sampling interval based on TLAB |
| // boundaries. Returns kUninitialized if there's no active TLAB. |
| intptr_t remaining_TLAB_interval() const; |
| |
| std::atomic<bool> schedule_thread_enable_ = false; |
| std::atomic<bool> schedule_thread_set_sampling_interval_ = false; |
| |
| // Protects sampling logic from modifications of callback_, sampling_interval, |
| // and enabled_ while collecting a sample. |
| // |
| // This lock should be acquired using WriteRwLocker when modifying static |
| // state, and should be acquired using ReadRwLocker when accessing static |
| // state from instances of HeapProfileSampler. |
| static RwLock* lock_; |
| static bool enabled_; |
| static Dart_HeapSamplingCreateCallback create_callback_; |
| static Dart_HeapSamplingDeleteCallback delete_callback_; |
| static intptr_t sampling_interval_; |
| |
| static constexpr intptr_t kUninitialized = -1; |
| static constexpr intptr_t kDefaultSamplingInterval = 512 * KB; |
| |
| bool thread_enabled_ = false; |
| intptr_t interval_to_next_sample_ = kUninitialized; |
| intptr_t next_tlab_offset_ = kUninitialized; |
| intptr_t last_sample_size_ = kUninitialized; |
| |
| Thread* thread_; |
| |
| DISALLOW_COPY_AND_ASSIGN(HeapProfileSampler); |
| }; |
| |
| } // namespace dart |
| |
| #endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER) |
| #endif // RUNTIME_VM_HEAP_SAMPLER_H_ |