From: Mikhail Kurinnoi Date: Fri, 19 May 2023 15:45:35 +0000 (+0300) Subject: Add sampling memory profiling. X-Git-Tag: accepted/tizen/unified/20230616.172407^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=420cd239d3e7866d1957dce0e60af8b14e3a9f78;p=sdk%2Ftools%2Fcoreprofiler.git Add sampling memory profiling. --- diff --git a/README.md b/README.md index ad29944..5ae4e71 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,19 @@ dumped (only timestamp of execution and configuration parameters is stored). Must be greater or equal to `PROF_MEMORY_MIN_SIZE_LIMIT` or will be disabled. Default value is `0` (limit is disabled). + - `PROF_MEMORY_SAMPLING_ENABLE` - sampling memory profiling mode by objects allocation size. + Default value is `false` (disabled). + + - `PROF_MEMORY_SAMPLING_INTERVAL` - sampling memory profiling interval (size) by objects allocation size. + Default value is `512`. + + - `PROF_MEMORY_SAMPLING_RANDOMIZATION` + Poisson Point Process is used for randomization of points on line segment (https://en.wikipedia.org/wiki/Poisson_point_process). + Using it we'll place points randomly on line with all allocations, so that more points get to larger allocations + and less points get to smaller allocations. This randomization is needed for cases when there are lots of small + allocations, which might be stepped over with constant sampling interval `PROF_MEMORY_SAMPLING_INTERVAL`. + Default value is `true`. + - `PROF_STACK_DEPTH_LIMIT` - track changes of execution stack for first `PROF_STACK_DEPTH_LIMIT` frames only. Default value is `0` (limit is disabled). _Current limitation: `PROF_EXECUTION_TRACE` or `PROF_MEMORY_TRACE` diff --git a/src/config/configurationmanager.cpp b/src/config/configurationmanager.cpp index 78ef694..4c31e0c 100644 --- a/src/config/configurationmanager.cpp +++ b/src/config/configurationmanager.cpp @@ -195,6 +195,10 @@ void ConfigurationManager::FetchConfig(ProfilerConfig &config) const FetchValue("PROF_GC_TRACE_GEN", new_config.GcGenerationBoundsTraceEnabled); FetchValue("PROF_GC_TRACE_ALT", new_config.GcAllocTableTraceEnabled); + FetchValue("PROF_MEMORY_SAMPLING_ENABLE", new_config.MemorySamplingEnabled); + FetchValue("PROF_MEMORY_SAMPLING_RANDOMIZATION", new_config.MemorySamplingIntervalRandomization); + FetchValue("PROF_MEMORY_SAMPLING_INTERVAL", new_config.MemorySamplingInterval); + // Apply changes to the current configuration. config = new_config; } diff --git a/src/config/profilerconfig.cpp b/src/config/profilerconfig.cpp index 0b3c69c..4613d02 100644 --- a/src/config/profilerconfig.cpp +++ b/src/config/profilerconfig.cpp @@ -39,6 +39,9 @@ ProfilerConfig::ProfilerConfig() , MemoryMaxSizeLimit(0) , GcGenerationBoundsTraceEnabled(false) , GcAllocTableTraceEnabled(false) + , MemorySamplingEnabled(false) + , MemorySamplingIntervalRandomization(true) + , MemorySamplingInterval(512) {} void ProfilerConfig::Validate() @@ -169,6 +172,11 @@ std::vector ProfilerConfig::Verify() warnings.push_back( "GC allocations table tracing is memory tracing option"); } + + if (MemorySamplingEnabled) + { + warnings.push_back("memory sampling is memory tracing option"); + } } if (MemoryMaxSizeLimit && MemoryMinSizeLimit > MemoryMaxSizeLimit) diff --git a/src/config/profilerconfig.h b/src/config/profilerconfig.h index 83c679e..d021611 100644 --- a/src/config/profilerconfig.h +++ b/src/config/profilerconfig.h @@ -77,6 +77,9 @@ struct ProfilerConfig unsigned long MemoryMaxSizeLimit; bool GcGenerationBoundsTraceEnabled; bool GcAllocTableTraceEnabled; + bool MemorySamplingEnabled; + bool MemorySamplingIntervalRandomization; + unsigned long MemorySamplingInterval; // // Validation and verification. diff --git a/src/info/objectinfo.cpp b/src/info/objectinfo.cpp index 46d04f7..909f81d 100644 --- a/src/info/objectinfo.cpp +++ b/src/info/objectinfo.cpp @@ -16,12 +16,38 @@ #include #include +#include +#include #include "profiler.h" #include "classstorage.h" #include "classinfo.h" #include "objectinfo.h" +template +class UniformRandomGenerator +{ + std::random_device rd; + std::default_random_engine generator; + std::uniform_real_distribution distribution; + +public: + + UniformRandomGenerator(T start, T end) + : generator(rd()), distribution(start, end) + {} + + double get() + { + return distribution(generator); + } +}; + +// distance to next sample in bytes (value <= 0 means that it's time to take a sample) +static thread_local intptr_t g_distanceToNextSample = 0; +// random number generator with values uniformly distributed on the [0.0,1.0) interval +static thread_local UniformRandomGenerator randgen(0.0, 1.0); + __forceinline HRESULT ObjectInfo::InitializeType( const Profiler &profiler, ClassStorage &storage, @@ -108,61 +134,60 @@ __forceinline HRESULT ObjectInfo::InitializeSize( HRESULT ObjectInfo::Initialize( const Profiler &profiler, - ClassStorage &storage) noexcept + ClassStorage &storage, + ClassID classId, + bool &skipByMemoryLimit, + bool *skipByMemorySampling) noexcept { HRESULT hrReturn = S_OK; HRESULT hr; - if (this->isInitialized) - { - return hrReturn; - } - _ASSERTE(this->id != 0); const ProfilerInfo &info = profiler.GetProfilerInfo(); - hr = this->InitializeType(profiler, storage, info); + hr = this->InitializeSize(profiler, info); if (FAILED(hr) && SUCCEEDED(hrReturn)) { hrReturn = hr; } - hr = this->InitializeSize(profiler, info); - if (FAILED(hr) && SUCCEEDED(hrReturn)) + if (this->size < profiler.GetConfig().MemoryMinSizeLimit || + (profiler.GetConfig().MemoryMaxSizeLimit && this->size > profiler.GetConfig().MemoryMaxSizeLimit)) { - hrReturn = hr; + skipByMemoryLimit = true; + return hrReturn; } - this->isInitialized = true; - return hrReturn; -} + if (skipByMemorySampling && profiler.GetConfig().MemorySamplingEnabled) + { + // See dotnet/heaptrack PR #121 for more comments and code logic explanation. -HRESULT ObjectInfo::Initialize( - const Profiler &profiler, - ClassStorage &storage, - ClassID classId) noexcept -{ - HRESULT hrReturn = S_OK; - HRESULT hr; + g_distanceToNextSample -= (intptr_t) this->size; - if (this->isInitialized) - { - return hrReturn; - } + if (g_distanceToNextSample > 0) + { + *skipByMemorySampling = true; + return hrReturn; + } - _ASSERTE(this->id != 0); - const ProfilerInfo &info = profiler.GetProfilerInfo(); + intptr_t samples = - g_distanceToNextSample / profiler.GetConfig().MemorySamplingInterval; + g_distanceToNextSample %= profiler.GetConfig().MemorySamplingInterval; - hr = this->InitializeSize(profiler, info); - if (FAILED(hr) && SUCCEEDED(hrReturn)) - { - hrReturn = hr; - } + do + { + intptr_t nextSamplingInterval = profiler.GetConfig().MemorySamplingInterval; + if (profiler.GetConfig().MemorySamplingIntervalRandomization) + { + double probability = randgen.get(); + nextSamplingInterval = uintptr_t(- std::log(probability) * profiler.GetConfig().MemorySamplingInterval); + } + + g_distanceToNextSample += nextSamplingInterval; + ++samples; + } + while (g_distanceToNextSample <= 0); - if (this->size < profiler.GetConfig().MemoryMinSizeLimit || - (profiler.GetConfig().MemoryMaxSizeLimit && this->size > profiler.GetConfig().MemoryMaxSizeLimit)) - { - return hrReturn; + this->size = samples * profiler.GetConfig().MemorySamplingInterval; } hr = this->InitializeTypeFromClassId(profiler, storage, classId); @@ -171,6 +196,5 @@ HRESULT ObjectInfo::Initialize( hrReturn = hr; } - this->isInitialized = true; return hrReturn; } diff --git a/src/info/objectinfo.h b/src/info/objectinfo.h index 97b8625..a1ddedb 100644 --- a/src/info/objectinfo.h +++ b/src/info/objectinfo.h @@ -32,7 +32,6 @@ struct ObjectInfo ObjectID id; SIZE_T size; ClassInfo* type; - bool isInitialized; private: HRESULT InitializeType( @@ -49,15 +48,13 @@ private: const Profiler &profiler, const ProfilerInfo &info) noexcept; - HRESULT Initialize( - const Profiler &profiler, - ClassStorage &storage) noexcept; - public: HRESULT Initialize( const Profiler &profiler, ClassStorage &storage, - ClassID classId) noexcept; + ClassID classId, + bool &skipByMemoryLimit, + bool *skipByMemorySampling) noexcept; }; #endif // _OBJECT_INFO_H_ diff --git a/src/profiler.cpp b/src/profiler.cpp index aa1bdb4..7f11adb 100644 --- a/src/profiler.cpp +++ b/src/profiler.cpp @@ -877,7 +877,12 @@ HRESULT STDMETHODCALLTYPE Profiler::MovedReferences( ULONG cObjectIDRangeLength[]) { LOG().Trace() << "MovedReferences()"; - return S_OK; + + HRESULT hr; + hr = m_memoryTrace.MovedReferences(cMovedObjectIDRanges, oldObjectIDRangeStart, + newObjectIDRangeStart, cObjectIDRangeLength); + + return hr; } HRESULT STDMETHODCALLTYPE Profiler::ObjectAllocated( diff --git a/src/trace/memorytrace.cpp b/src/trace/memorytrace.cpp index 5217f3c..bfc04e9 100644 --- a/src/trace/memorytrace.cpp +++ b/src/trace/memorytrace.cpp @@ -137,16 +137,36 @@ HRESULT MemoryTrace::InitAllocInfoByTypes(AllocTable &allocInfoByTypes) noexcept HRESULT hr = S_OK; try { + std::lock_guard lock(m_sampledObjectsMutex); + std::unordered_map survivedSampledObjects; + auto storage_lock = m_profiler.GetCommonTrace().GetClassStorage(); for (const auto &objectWithClass : m_survivedObjects) { ObjectID objectId = objectWithClass.first; + + SIZE_T sampledSize = 0; + if (m_profiler.GetConfig().MemorySamplingEnabled) + { + auto find = m_sampledObjects.find(objectId); + if (find == m_sampledObjects.end()) + { + continue; + } + else + { + sampledSize = find->second; + survivedSampledObjects.emplace(std::move(*find)); + } + } + ClassID classId = objectWithClass.second; ObjectInfo objInfo = {}; objInfo.id = objectId; + bool skipByMemoryLimit = false; // NOTE: it is OK to use classId == 0 here. - hr = objInfo.Initialize(m_profiler, *storage_lock, classId); + hr = objInfo.Initialize(m_profiler, *storage_lock, classId, skipByMemoryLimit, nullptr); if (FAILED(hr)) { throw std::runtime_error( @@ -155,12 +175,17 @@ HRESULT MemoryTrace::InitAllocInfoByTypes(AllocTable &allocInfoByTypes) noexcept ); } - if (objInfo.size < m_profiler.GetConfig().MemoryMinSizeLimit || - (m_profiler.GetConfig().MemoryMaxSizeLimit && objInfo.size > m_profiler.GetConfig().MemoryMaxSizeLimit)) + if (skipByMemoryLimit) { + assert(!m_profiler.GetConfig().MemorySamplingEnabled && !sampledSize); continue; } + if (sampledSize) + { + objInfo.size = sampledSize; + } + _ASSERTE(objInfo.type != nullptr); if (objInfo.type->needPrintLoadFinished) { @@ -178,6 +203,11 @@ HRESULT MemoryTrace::InitAllocInfoByTypes(AllocTable &allocInfoByTypes) noexcept allocInfo.allocCount++; allocInfo.memSize += objInfo.size; } + + if (m_profiler.GetConfig().MemorySamplingEnabled) + { + m_sampledObjects = std::move(survivedSampledObjects); + } } catch (const std::exception &e) { @@ -202,9 +232,11 @@ HRESULT MemoryTrace::ObjectAllocated( { ObjectInfo objInfo = {}; objInfo.id = objectId; + bool skipByMemoryLimit = false; + bool skipByMemorySampling = false; { auto storage_lock = m_profiler.GetCommonTrace().GetClassStorage(); - hr = objInfo.Initialize(m_profiler, *storage_lock, classId); + hr = objInfo.Initialize(m_profiler, *storage_lock, classId, skipByMemoryLimit, &skipByMemorySampling); } if (FAILED(hr)) { @@ -214,12 +246,17 @@ HRESULT MemoryTrace::ObjectAllocated( ); } - if (objInfo.size < m_profiler.GetConfig().MemoryMinSizeLimit || - (m_profiler.GetConfig().MemoryMaxSizeLimit && objInfo.size > m_profiler.GetConfig().MemoryMaxSizeLimit)) + if (skipByMemoryLimit || skipByMemorySampling) { return hr; } + if (m_profiler.GetConfig().MemorySamplingEnabled) + { + std::lock_guard lock(m_sampledObjectsMutex); + m_sampledObjects.emplace(objectId, objInfo.size); + } + _ASSERTE(objInfo.type != nullptr); if (objInfo.type->needPrintLoadFinished) { @@ -265,6 +302,47 @@ HRESULT MemoryTrace::ObjectAllocated( return hr; } +HRESULT MemoryTrace::MovedReferences( + ULONG cMovedObjectIDRanges, + ObjectID oldObjectIDRangeStart[], + ObjectID newObjectIDRangeStart[], + ULONG cObjectIDRangeLength[]) noexcept +{ + if (!m_profiler.GetConfig().MemorySamplingEnabled) + return S_OK; + + std::lock_guard lock(m_sampledObjectsMutex); + + std::map oldObjects(m_sampledObjects.begin(), m_sampledObjects.end()); + m_sampledObjects.clear(); + + for (ULONG i = 0; i < cMovedObjectIDRanges; i++) + { + auto start = oldObjects.lower_bound(oldObjectIDRangeStart[i]); + + for (auto it = start; it != oldObjects.end(); ) + { + // https://github.com/dotnet/runtime/blob/aef327f3b418bf5dd4f25083aff160c9cdf4b159/src/coreclr/inc/corprof.idl#L1666-L1669 + if (oldObjectIDRangeStart[i] <= it->first && it->first < oldObjectIDRangeStart[i] + cObjectIDRangeLength[i]) + { + m_sampledObjects.emplace(it->first - oldObjectIDRangeStart[i] + newObjectIDRangeStart[i], it->second); + it = oldObjects.erase(it); + continue; + } + + break; + } + } + + for (auto &object : oldObjects) + { + m_sampledObjects.emplace(std::move(object)); + } + oldObjects.clear(); + + return S_OK; +} + HRESULT MemoryTrace::GarbageCollectionStarted( int cGenerations, BOOL generationCollected[], diff --git a/src/trace/memorytrace.h b/src/trace/memorytrace.h index d9c9b0a..da45a62 100644 --- a/src/trace/memorytrace.h +++ b/src/trace/memorytrace.h @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -64,6 +65,12 @@ public: ObjectID objectId, ClassID classId) noexcept; + HRESULT MovedReferences( + ULONG cMovedObjectIDRanges, + ObjectID oldObjectIDRangeStart[], + ObjectID newObjectIDRangeStart[], + ULONG cObjectIDRangeLength[]) noexcept; + HRESULT GarbageCollectionStarted( int cGenerations, BOOL generationCollected[], @@ -81,6 +88,9 @@ private: bool m_objectTrackingSuspended; bool m_objectTrackingFailure; std::vector> m_survivedObjects; + + std::mutex m_sampledObjectsMutex; + std::unordered_map m_sampledObjects; }; #endif // _MEMORY_TRACE_H_ diff --git a/src/tracelog/tracelog.cpp b/src/tracelog/tracelog.cpp index a468bc4..c3879f0 100644 --- a/src/tracelog/tracelog.cpp +++ b/src/tracelog/tracelog.cpp @@ -160,6 +160,9 @@ public: m_tracefmt.log("prf cfg", g_tls_ss).str("MemoryMinSizeLimit").config(config.MemoryMinSizeLimit).end(); m_tracefmt.log("prf cfg", g_tls_ss).str("MemoryMaxSizeLimit").config(config.MemoryMaxSizeLimit).end(); m_tracefmt.log("prf cfg", g_tls_ss).str("GcAllocTableTraceEnabled").config(config.GcAllocTableTraceEnabled).end(); + m_tracefmt.log("prf cfg", g_tls_ss).str("MemorySamplingEnabled").config(config.MemorySamplingEnabled).end(); + m_tracefmt.log("prf cfg", g_tls_ss).str("MemorySamplingIntervalRandomization").config(config.MemorySamplingIntervalRandomization).end(); + m_tracefmt.log("prf cfg", g_tls_ss).str("MemorySamplingInterval").config(config.MemorySamplingInterval).end(); AddTLSDataToQueue(); }