blob: e5c02a4f07048d8c7cabf93198ce1316956b062d [file] [log] [blame] [edit]
// 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_