From: Dmitri Botcharnikov Date: Tue, 29 Aug 2017 14:48:17 +0000 (+0300) Subject: Fix Hot Lines and add GC tracing. X-Git-Tag: submit/tizen/20180619.075036~15 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=ba3913864447aa65a46f82d7ce3ab6d33bb6e9ea;p=sdk%2Ftools%2Fcoreprofiler.git Fix Hot Lines and add GC tracing. --- diff --git a/CMakeLists.txt b/CMakeLists.txt index ae26daf..986d896 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,26 @@ cmake_minimum_required(VERSION 2.8.12.2) project(CoreProfiler) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wno-null-conversion") +# Use uppercase CMAKE_BUILD_TYPE for the string comparisons below +string(TOUPPER ${CMAKE_BUILD_TYPE} UPPERCASE_CMAKE_BUILD_TYPE) + +# For single-configuration toolset +# set the different configuration defines. +if (UPPERCASE_CMAKE_BUILD_TYPE STREQUAL DEBUG) + # First DEBUG + set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS ${CLR_DEFINES_DEBUG_INIT}) +elseif (UPPERCASE_CMAKE_BUILD_TYPE STREQUAL CHECKED) + # Then CHECKED + set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS ${CLR_DEFINES_CHECKED_INIT}) +elseif (UPPERCASE_CMAKE_BUILD_TYPE STREQUAL RELEASE) + # Then RELEASE + set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS ${CLR_DEFINES_RELEASE_INIT}) +elseif (UPPERCASE_CMAKE_BUILD_TYPE STREQUAL RELWITHDEBINFO) + # And then RELWITHDEBINFO + set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS ${CLR_DEFINES_RELWITHDEBINFO_INIT}) +else () + message(FATAL_ERROR "Unknown build type! Set CMAKE_BUILD_TYPE to DEBUG, CHECKED, RELEASE, or RELWITHDEBINFO!") +endif () set(CLR_CMAKE_PLATFORM_UNIX 1) diff --git a/clang-compiler-override-arm.txt b/clang-compiler-override-arm.txt new file mode 100644 index 0000000..da25715 --- /dev/null +++ b/clang-compiler-override-arm.txt @@ -0,0 +1,20 @@ +SET (CMAKE_C_FLAGS_INIT "-Wall -std=c11") +SET (CMAKE_C_FLAGS_DEBUG_INIT "-g -O0") +SET (CLR_C_FLAGS_CHECKED_INIT "-g -O1") +# Refer to the below instruction to support __thread with -O2/-O3 on Linux/ARM +# https://github.com/dotnet/coreclr/blob/master/Documentation/building/linux-instructions.md +SET (CMAKE_C_FLAGS_RELEASE_INIT "-g -O1") +SET (CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "-g -O1") + +SET (CMAKE_CXX_FLAGS_INIT "-Wall -Wno-null-conversion -std=c++11") +SET (CMAKE_CXX_FLAGS_DEBUG_INIT "-g -O0") +SET (CLR_CXX_FLAGS_CHECKED_INIT "-g -O1") +SET (CMAKE_CXX_FLAGS_RELEASE_INIT "-g -O1") +SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "-g -O1") + +SET (CLR_DEFINES_DEBUG_INIT DEBUG _DEBUG _DBG URTBLDENV_FRIENDLY=Checked BUILDENV_CHECKED=1) +SET (CLR_DEFINES_CHECKED_INIT DEBUG _DEBUG _DBG URTBLDENV_FRIENDLY=Checked BUILDENV_CHECKED=1) +SET (CLR_DEFINES_RELEASE_INIT NDEBUG URTBLDENV_FRIENDLY=Retail) +SET (CLR_DEFINES_RELWITHDEBINFO_INIT NDEBUG URTBLDENV_FRIENDLY=Retail) + +SET (CMAKE_INSTALL_PREFIX $ENV{__CMakeBinDir}) diff --git a/clang-compiler-override.txt b/clang-compiler-override.txt new file mode 100644 index 0000000..ee1e63b --- /dev/null +++ b/clang-compiler-override.txt @@ -0,0 +1,20 @@ +SET (CMAKE_C_FLAGS_INIT "-Wall -std=c11") +SET (CMAKE_C_FLAGS_DEBUG_INIT "-g -O0") +SET (CLR_C_FLAGS_CHECKED_INIT "-g -O2") +# Refer to the below instruction to support __thread with -O2/-O3 on Linux/ARM +# https://github.com/dotnet/coreclr/blob/master/Documentation/building/linux-instructions.md +SET (CMAKE_C_FLAGS_RELEASE_INIT "-g -O3") +SET (CMAKE_C_FLAGS_RELWITHDEBINFO_INIT "-g -O2") + +SET (CMAKE_CXX_FLAGS_INIT "-Wall -Wno-null-conversion -std=c++11") +SET (CMAKE_CXX_FLAGS_DEBUG_INIT "-g -O0") +SET (CLR_CXX_FLAGS_CHECKED_INIT "-g -O2") +SET (CMAKE_CXX_FLAGS_RELEASE_INIT "-g -O3") +SET (CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "-g -O2") + +SET (CLR_DEFINES_DEBUG_INIT DEBUG _DEBUG _DBG URTBLDENV_FRIENDLY=Checked BUILDENV_CHECKED=1) +SET (CLR_DEFINES_CHECKED_INIT DEBUG _DEBUG _DBG URTBLDENV_FRIENDLY=Checked BUILDENV_CHECKED=1) +SET (CLR_DEFINES_RELEASE_INIT NDEBUG URTBLDENV_FRIENDLY=Retail) +SET (CLR_DEFINES_RELWITHDEBINFO_INIT NDEBUG URTBLDENV_FRIENDLY=Retail) + +SET (CMAKE_INSTALL_PREFIX $ENV{__CMakeBinDir}) diff --git a/packaging/coreprofiler.spec b/packaging/coreprofiler.spec index 405789f..18f2bc1 100644 --- a/packaging/coreprofiler.spec +++ b/packaging/coreprofiler.spec @@ -1,7 +1,9 @@ +%{!?buildtype: %define buildtype Release} + Name: coreprofiler Summary: PUT SUMMARY HERE Version: 1.0.0 -Release: 1 +Release: 1.rt Group: Development/Toolchain License: MIT Source0: coreprofiler.tar.gz @@ -70,6 +72,12 @@ export CXXFLAGS=" -B${LLVM_LIBDIR} -B${GCC_INSTALL_DIR} -Wno-deprecated-declarat export CPLUS_INCLUDE_PATH="${LLVM_INCLUDEDIR}/llvm/:${LLVM_INCLUDEDIR}/llvm-c/:${CLANG_HEADERS}:${GPP_INCLUDE_PATHS}:${CLANG_INCLUDE_PATHS}" export C_INCLUDE_PATH="${LLVM_INCLUDEDIR}/llvm-c/:%{_includedir}" +%ifarch armv7l +%define _overridefile clang-compiler-override-arm.txt +%else +%define _overridefile clang-compiler-override.txt +%endif + mkdir build cd build cmake ../coreprofiler \ @@ -77,7 +85,9 @@ cmake ../coreprofiler \ -DCMAKE_CXX_COMPILER=clang++ \ -DCLR_BIN_DIR=%{_datarootdir}/%{netcoreappdir} \ -DCLR_SRC_DIR=%{_datarootdir}/%{netcoreappdir} \ - -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_BUILD_TYPE=%{buildtype} \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -DCMAKE_USER_MAKE_RULES_OVERRIDE=../coreprofiler/%{_overridefile} \ -DCLR_CMAKE_TARGET_ARCH_%{ARCH}=1 make %{?jobs:-j%jobs} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a938ad5..0c44b45 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,7 +20,7 @@ set(CLR_INCLUDE_DIR ${CLR_BIN_DIR}/inc ${CLR_SRC_DIR}/src/pal/inc/rt ${CLR_SRC_DIR}/src/pal/prebuilt/inc - ${CLR_SRC_DIR}/src/pal/inc + ${CLR_SRC_DIR}/src/pal/inc ${CLR_SRC_DIR}/src/inc) include_directories(AFTER ${CLR_INCLUDE_DIR}) @@ -38,17 +38,16 @@ set(PROFILER_SOURCES_COMMON dllmain.cpp info/classinfo.cpp info/functioninfo.cpp + info/objectinfo.cpp misc/localtime.cpp misc/sigaction.cpp profiler.cpp profilerinfo.cpp profilermanager.cpp sync/shared_mutex.cpp - trace/basetrace.cpp trace/commontrace.cpp trace/cputrace.cpp trace/executiontrace.cpp - trace/eventchannel.cpp trace/memorytrace.cpp tracelog.cpp ) diff --git a/src/amd64/archhelpers.cpp b/src/amd64/archhelpers.cpp index 45513e0..034dffe 100644 --- a/src/amd64/archhelpers.cpp +++ b/src/amd64/archhelpers.cpp @@ -32,9 +32,6 @@ HRESULT ContextToStackSnapshotContext( winContext->R15 = mc->gregs[REG_R15]; winContext->EFlags = mc->gregs[REG_EFL]; winContext->Rip = mc->gregs[REG_RIP]; - winContext->SegCs = (*((WORD *)mc->gregs[REG_CSGSFS] + 0)); - winContext->SegGs = (*((WORD *)mc->gregs[REG_CSGSFS] + 1)); - winContext->SegFs = (*((WORD *)mc->gregs[REG_CSGSFS] + 2)); } return S_OK; diff --git a/src/config/environmentconfigprovider.cpp b/src/config/environmentconfigprovider.cpp index d32aaf7..c2515ad 100644 --- a/src/config/environmentconfigprovider.cpp +++ b/src/config/environmentconfigprovider.cpp @@ -124,13 +124,22 @@ void EnvironmentConfigProvider::FetchConfig(ProfilerConfig &config) const FetchValue("PROF_EXECUTION_TRACE", new_config.ExecutionTraceEnabled); FetchValue("PROF_MEMORY_TRACE", new_config.MemoryTraceEnabled); - if (!new_config.MemoryTraceEnabled) + if (new_config.MemoryTraceEnabled && + new_config.ExecutionTraceEnabled && + new_config.CollectionMethod == CollectionMethod::Sampling) { - new_config.StackTrackingEnabled = false; + new_config.StackTrackingEnabled = true; } FetchValue("PROF_STACK_TRACK", new_config.StackTrackingEnabled); + bool GcTraceEnabled; + if (FetchValue("PROF_GC_TRACE", GcTraceEnabled)) + { + new_config.GcAllocTableTraceEnabled = GcTraceEnabled; + } + FetchValue("PROF_GC_TRACE_ALT", new_config.GcAllocTableTraceEnabled); + // Apply changes to the current configuration. config = new_config; } diff --git a/src/config/profilerconfig.cpp b/src/config/profilerconfig.cpp index 4bd262b..8c14f08 100644 --- a/src/config/profilerconfig.cpp +++ b/src/config/profilerconfig.cpp @@ -17,7 +17,8 @@ ProfilerConfig::ProfilerConfig() , CpuTraceTimeoutMs(10) , ExecutionTraceEnabled(false) , MemoryTraceEnabled(false) - , StackTrackingEnabled(true) + , StackTrackingEnabled(false) + , GcAllocTableTraceEnabled(false) {} void ProfilerConfig::Validate() @@ -47,14 +48,6 @@ std::vector ProfilerConfig::Verify() { // Instrumentation specific options verification. } - else - { - if (StackTrackingEnabled) - { - warnings.push_back( - "stack tracking option is redundant for instrumentation"); - } - } if (CollectionMethod != CollectionMethod::Sampling) { @@ -72,6 +65,13 @@ std::vector ProfilerConfig::Verify() // line tracing above. warnings.push_back("hight granularity option requires sampling"); } + + if (StackTrackingEnabled) + { + StackTrackingEnabled = false; + warnings.push_back( + "stack tracking option requires sampling and turned off"); + } } if (CollectionMethod == CollectionMethod::None) @@ -120,6 +120,12 @@ std::vector ProfilerConfig::Verify() { warnings.push_back("stack tracking is memory tracing option"); } + + if (GcAllocTableTraceEnabled) + { + warnings.push_back( + "GC allocations table tracing is memory tracing option"); + } } return warnings; diff --git a/src/config/profilerconfig.h b/src/config/profilerconfig.h index 93bb604..8c97d5f 100644 --- a/src/config/profilerconfig.h +++ b/src/config/profilerconfig.h @@ -56,6 +56,7 @@ struct ProfilerConfig bool MemoryTraceEnabled; bool StackTrackingEnabled; + bool GcAllocTableTraceEnabled; // // Validation and verification. diff --git a/src/info/functioninfo.cpp b/src/info/functioninfo.cpp index 11977c1..51a9854 100644 --- a/src/info/functioninfo.cpp +++ b/src/info/functioninfo.cpp @@ -593,6 +593,7 @@ __forceinline HRESULT FunctionInfo::InitializeOwnerClassFromClassId( } catch (const std::exception &e) { + this->ownerClass = nullptr; hr = profiler.HandleException(e); } @@ -614,6 +615,7 @@ __forceinline HRESULT FunctionInfo::InitializeOwnerClassFromClassToken( } catch (const std::exception &e) { + this->ownerClass = nullptr; hr = profiler.HandleException(e); } diff --git a/src/info/functioninfo.h b/src/info/functioninfo.h index 2fb4071..2485654 100644 --- a/src/info/functioninfo.h +++ b/src/info/functioninfo.h @@ -39,7 +39,6 @@ struct FunctionInfo : public MappedInfo bool isNamePrinted; private: - static void ParseElementType( const Profiler &profiler, IMetaDataImport *pMDImport, diff --git a/src/info/objectinfo.cpp b/src/info/objectinfo.cpp new file mode 100644 index 0000000..b023cca --- /dev/null +++ b/src/info/objectinfo.cpp @@ -0,0 +1,154 @@ +#include +#include + +#include "profiler.h" +#include "classstorage.h" +#include "classinfo.h" +#include "objectinfo.h" + +__forceinline HRESULT ObjectInfo::InitializeType( + const Profiler &profiler, + ClassStorage &storage, + const ProfilerInfo &info) noexcept +{ + HRESULT hr = S_OK; + + try + { + ClassID classId; + hr = info.v1()->GetClassFromObject(this->id, &classId); + if (FAILED(hr)) + { + throw HresultException( + "ObjectInfo::InitializeType(): GetClassFromObject()", hr + ); + } + + this->type = &storage.Place(classId).first; + hr = this->type->Initialize(profiler, storage); + } + catch (const std::exception &e) + { + this->type = nullptr; + hr = profiler.HandleException(e); + } + + return hr; +} + +__forceinline HRESULT ObjectInfo::InitializeTypeFromClassId( + const Profiler &profiler, + ClassStorage &storage, + ClassID classId) noexcept +{ + HRESULT hr = S_OK; + + try + { + this->type = &storage.Place(classId).first; + hr = this->type->Initialize(profiler, storage); + } + catch (const std::exception &e) + { + this->type = nullptr; + hr = profiler.HandleException(e); + } + + return hr; +} + +__forceinline HRESULT ObjectInfo::InitializeSize( + const Profiler &profiler, + const ProfilerInfo &info) noexcept +{ + HRESULT hr = S_OK; + + try + { + if (info.version() >= 4) + { + hr = info.v4()->GetObjectSize2(this->id, &this->size); + } + else + { + ULONG size = 0; + hr = info.v1()->GetObjectSize(this->id, &size); + this->size = size; + } + if (FAILED(hr)) + { + throw HresultException( + "ObjectInfo::Initialize(): GetObjectSize()", hr); + } + } + catch (const std::exception &e) + { + this->size = 0; + hr = profiler.HandleException(e); + } + + return hr; +} + +HRESULT ObjectInfo::Initialize( + const Profiler &profiler, + ClassStorage &storage) 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); + if (FAILED(hr) && SUCCEEDED(hrReturn)) + { + hrReturn = hr; + } + + hr = this->InitializeSize(profiler, info); + if (FAILED(hr) && SUCCEEDED(hrReturn)) + { + hrReturn = hr; + } + + this->isInitialized = true; + return hrReturn; +} + +HRESULT ObjectInfo::Initialize( + const Profiler &profiler, + ClassStorage &storage, + ClassID classId) noexcept +{ + HRESULT hrReturn = S_OK; + HRESULT hr; + + if (this->isInitialized) + { + return hrReturn; + } + + _ASSERTE(this->id != 0); + const ProfilerInfo &info = profiler.GetProfilerInfo(); + + hr = this->InitializeTypeFromClassId(profiler, storage, classId); + if (FAILED(hr) && SUCCEEDED(hrReturn)) + { + hrReturn = hr; + } + + hr = this->InitializeSize(profiler, info); + if (FAILED(hr) && SUCCEEDED(hrReturn)) + { + hrReturn = hr; + } + + this->isInitialized = true; + return hrReturn; +} diff --git a/src/info/objectinfo.h b/src/info/objectinfo.h new file mode 100644 index 0000000..57175ea --- /dev/null +++ b/src/info/objectinfo.h @@ -0,0 +1,47 @@ +#ifndef _OBJECT_INFO_H_ +#define _OBJECT_INFO_H_ + +#include +#include +#include + +class Profiler; + +class ProfilerInfo; + +class ClassStorage; + +struct ObjectInfo +{ + ObjectID id; + SIZE_T size; + ClassInfo* type; + bool isInitialized; + +private: + HRESULT InitializeType( + const Profiler &profiler, + ClassStorage &storage, + const ProfilerInfo &info) noexcept; + + HRESULT InitializeTypeFromClassId( + const Profiler &profiler, + ClassStorage &storage, + ClassID classId) noexcept; + + HRESULT InitializeSize( + const Profiler &profiler, + const ProfilerInfo &info) noexcept; + +public: + HRESULT Initialize( + const Profiler &profiler, + ClassStorage &storage) noexcept; + + HRESULT Initialize( + const Profiler &profiler, + ClassStorage &storage, + ClassID classId) noexcept; +}; + +#endif // _OBJECT_INFO_H_ diff --git a/src/log.h b/src/log.h index 5e980af..0426060 100644 --- a/src/log.h +++ b/src/log.h @@ -41,22 +41,21 @@ static inline const char *LogLevelName(LogLevel level) } } -#ifdef _DEBUG - #include class Log { private: + template class LogLine { public: - LogLine(LogLevel level, std::ostream *stream = nullptr) + LogLine(std::ostream *stream = nullptr) : m_stream(stream) { if (m_stream) { - *m_stream << "[" << LogLevelName(level) << "]\t"; + *m_stream << "[" << LogLevelName(L) << "]\t"; } } @@ -94,6 +93,29 @@ private: std::ostream *m_stream; }; + template<> + class LogLine + { + public: + LogLine() = default; + + LogLine(const LogLine&) = delete; + + LogLine &operator=(const LogLine&) = delete; + + LogLine(LogLine &&other) = default; + + LogLine &operator=(LogLine&&) = delete; + + ~LogLine() = default; + + template + LogLine &operator<<(T value) + { + return *this; + } + }; + public: Log(LogLevel level, const std::string &filename) : m_level(level) @@ -180,35 +202,47 @@ public: m_stream->exceptions(except); } - LogLine Fatal() + LogLine Fatal() { return DoLog(); } - LogLine Error() + LogLine Error() { return DoLog(); } - LogLine Warn() + LogLine Warn() { return DoLog(); } - LogLine Info() + LogLine Info() { return DoLog(); } - LogLine Debug() +#ifdef NDEBUG + LogLine Debug() + { + return LogLine(); + } + + LogLine Trace() + { + return LogLine(); + } +#else + LogLine Debug() { return DoLog(); } - LogLine Trace() + LogLine Trace() { return DoLog(); } +#endif // NDEBUG private: LogLevel m_level; @@ -216,108 +250,20 @@ private: bool m_stream_owner; template - LogLine DoLog() + LogLine DoLog() { // With RVO optimization LogLine destructor will be called only once. // Otherwise with overloaded move constructor only last destructor call // will print std::endl. if (m_level >= L) { - return LogLine(L, m_stream); + return LogLine(m_stream); } else { - return LogLine(L); - } - } -}; - -#else // !_DEBUG - -class Log -{ -private: - class LogLine - { - public: - LogLine() {} - - LogLine(const LogLine&) = delete; - - LogLine &operator=(const LogLine&) = delete; - - LogLine(LogLine&&) = default; - - LogLine &operator=(LogLine&&) = delete; - - template - LogLine &operator<<(T value) - { - return *this; + return LogLine(); } - }; - -public: - Log(LogLevel, const std::string&) {} - - explicit Log(const std::string&) {} - - Log(LogLevel, std::ostream&) {} - - explicit Log(LogLevel) {} - - explicit Log(std::ostream&) {} - - Log() {} - - Log(const Log&) = delete; - - Log &operator=(const Log&) = delete; - - Log(Log&&) = default; - - Log &operator=(Log&&) = default; - - void swap(Log &other) {} - - std::ostream::iostate exceptions() const - { - return std::ostream::goodbit; - } - - void exceptions(std::ostream::iostate except) {} - - LogLine Fatal() - { - return LogLine(); - } - - LogLine Error() - { - return LogLine(); - } - - LogLine Warn() - { - return LogLine(); - } - - LogLine Info() - { - return LogLine(); - } - - LogLine Debug() - { - return LogLine(); - } - - LogLine Trace() - { - return LogLine(); } }; -#endif // !_DEBUG - #endif // _LOG_H_ diff --git a/src/profiler.cpp b/src/profiler.cpp index 01f574a..c0998ce 100644 --- a/src/profiler.cpp +++ b/src/profiler.cpp @@ -9,7 +9,12 @@ #include "localtime.h" #include "profilermanager.h" + #include "profiler.h" +#include "commontrace.h" +#include "cputrace.h" +#include "executiontrace.h" +#include "memorytrace.h" // static HRESULT Profiler::CreateObject( @@ -141,23 +146,6 @@ Profiler::~Profiler() { } -Log &Profiler::LOG() const noexcept -{ - return const_cast(m_logger); -} - -ITraceLog &Profiler::TRACE() const noexcept -{ - // NOTE: default-constructed TraceLog object should not be used for output! - _ASSERTE(m_traceLog != nullptr); - return const_cast(*m_traceLog); -} - -DWORD Profiler::GetTickCountFromInit() const noexcept -{ - return GetTickCount() - m_firstTickCount; -} - HRESULT Profiler::HandleException(const std::exception &e) const noexcept { // Find type of exception. @@ -242,36 +230,6 @@ void Profiler::HandleHresult( this->HandleException(HresultException(what_arg, hr)); } -ProfilerConfig &Profiler::GetConfig() noexcept -{ - return m_profConfig; -} - -const ProfilerInfo &Profiler::GetProfilerInfo() const noexcept -{ - return m_info; -} - -CommonTrace &Profiler::GetCommonTrace() noexcept -{ - return m_commonTrace; -} - -CpuTrace &Profiler::GetCpuTrace() noexcept -{ - return m_cpuTrace; -} - -ExecutionTrace &Profiler::GetExecutionTrace() noexcept -{ - return m_executionTrace; -} - -MemoryTrace &Profiler::GetMemoryTrace() noexcept -{ - return m_memoryTrace; -} - void Profiler::SetupLogging(LoggerConfig &config) { if (config.OutputStream == LoggerOutputStream::Stdout) @@ -901,7 +859,14 @@ HRESULT STDMETHODCALLTYPE Profiler::ObjectReferences( ObjectID objectRefIds[]) { LOG().Trace() << "ObjectReferences()"; - return S_OK; + + HRESULT hr; + hr = m_memoryTrace.ObjectReferences( + objectId, classId, cObjectRefs, objectRefIds); + // NOTE: for more then one callback handler check HR and use S_OK + // if at least one handler returned S_OK to continue callback calls. + + return hr; } HRESULT STDMETHODCALLTYPE Profiler::RootReferences( @@ -912,14 +877,18 @@ HRESULT STDMETHODCALLTYPE Profiler::RootReferences( return S_OK; } - HRESULT STDMETHODCALLTYPE Profiler::GarbageCollectionStarted( int cGenerations, BOOL generationCollected[], COR_PRF_GC_REASON reason) { LOG().Trace() << "GarbageCollectionStarted()"; - return S_OK; + + HRESULT hr; + hr = m_memoryTrace.GarbageCollectionStarted( + cGenerations, generationCollected, reason); + + return hr; } HRESULT STDMETHODCALLTYPE Profiler::SurvivingReferences( @@ -934,6 +903,10 @@ HRESULT STDMETHODCALLTYPE Profiler::SurvivingReferences( HRESULT STDMETHODCALLTYPE Profiler::GarbageCollectionFinished() { LOG().Trace() << "GarbageCollectionFinished()"; + + HRESULT hr; + hr = m_memoryTrace.GarbageCollectionFinished(); + return S_OK; } diff --git a/src/profiler.h b/src/profiler.h index c84588c..3dbf981 100644 --- a/src/profiler.h +++ b/src/profiler.h @@ -64,15 +64,26 @@ private: public: // Returns mutable reference to the Logger even for a constant // reference to the Profiler. - Log &LOG() const noexcept; + Log &LOG() const noexcept + { + return m_logger; + } // Returns mutable reference to the TraceLog even for a constant // reference to the Profiler. - ITraceLog &TRACE() const noexcept; + ITraceLog &TRACE() const noexcept + { + // NOTE: default-constructed TraceLog object should not be used for output! + _ASSERTE(m_traceLog != nullptr); + return *m_traceLog; + } // Retrieves the number of milliseconds that have elapsed since the Profiler // was initialized. - DWORD GetTickCountFromInit() const noexcept; + DWORD GetTickCountFromInit() const noexcept + { + return GetTickCount() - m_firstTickCount; + } // Check type of the exception, send corresponding information to the log // and return HRESULT related to this exception. @@ -90,17 +101,35 @@ public: // Simple Getters. // - ProfilerConfig &GetConfig() noexcept; + const ProfilerConfig &GetConfig() const noexcept + { + return m_profConfig; + } - const ProfilerInfo &GetProfilerInfo() const noexcept; + const ProfilerInfo &GetProfilerInfo() const noexcept + { + return m_info; + } - CommonTrace &GetCommonTrace() noexcept; + CommonTrace &GetCommonTrace() const noexcept + { + return m_commonTrace; + } - CpuTrace &GetCpuTrace() noexcept; + CpuTrace &GetCpuTrace() const noexcept + { + return m_cpuTrace; + } - ExecutionTrace &GetExecutionTrace() noexcept; + ExecutionTrace &GetExecutionTrace() const noexcept + { + return m_executionTrace; + } - MemoryTrace &GetMemoryTrace() noexcept; + MemoryTrace &GetMemoryTrace() const noexcept + { + return m_memoryTrace; + } private: // @@ -475,7 +504,7 @@ public: private: LONG m_cRef; - Log m_logger; + mutable Log m_logger; BOOL m_initialized; BOOL m_shutdowned; @@ -485,12 +514,12 @@ private: ProfilerConfig m_profConfig; ProfilerInfo m_info; - std::unique_ptr m_traceLog; + mutable std::unique_ptr m_traceLog; - CommonTrace m_commonTrace; - CpuTrace m_cpuTrace; - ExecutionTrace m_executionTrace; - MemoryTrace m_memoryTrace; + mutable CommonTrace m_commonTrace; + mutable CpuTrace m_cpuTrace; + mutable ExecutionTrace m_executionTrace; + mutable MemoryTrace m_memoryTrace; DWORD m_firstTickCount; }; diff --git a/src/profilerinfo.cpp b/src/profilerinfo.cpp index e0ad714..2be2398 100644 --- a/src/profilerinfo.cpp +++ b/src/profilerinfo.cpp @@ -177,50 +177,3 @@ void ProfilerInfo::Reset() noexcept m_version = 0; } } - -unsigned int ProfilerInfo::version() const noexcept -{ - return m_version; -} - -ICorProfilerInfo *ProfilerInfo::v1() const noexcept -{ - _ASSERTE(m_version >= 1); - return m_pProfilerInfo; -} - -ICorProfilerInfo2 *ProfilerInfo::v2() const noexcept -{ - _ASSERTE(m_version >= 2); - return m_pProfilerInfo2; -} - -ICorProfilerInfo3 *ProfilerInfo::v3() const noexcept -{ - _ASSERTE(m_version >= 3); - return m_pProfilerInfo3; -} - -ICorProfilerInfo4 *ProfilerInfo::v4() const noexcept -{ - _ASSERTE(m_version >= 4); - return m_pProfilerInfo4; -} - -ICorProfilerInfo5 *ProfilerInfo::v5() const noexcept -{ - _ASSERTE(m_version >= 5); - return m_pProfilerInfo5; -} - -ICorProfilerInfo6 *ProfilerInfo::v6() const noexcept -{ - _ASSERTE(m_version >= 6); - return m_pProfilerInfo6; -} - -ICorProfilerInfo7 *ProfilerInfo::v7() const noexcept -{ - _ASSERTE(m_version >= 7); - return m_pProfilerInfo7; -} diff --git a/src/profilerinfo.h b/src/profilerinfo.h index 7008b8c..d2870b0 100644 --- a/src/profilerinfo.h +++ b/src/profilerinfo.h @@ -20,20 +20,57 @@ public: // Get version of the Profiler Info API. Zero value means that no API // versions is supported. - unsigned int version() const noexcept; + unsigned int version() const noexcept + { + return m_version; + } // // These methods provide access to a specific version of the Profiler Info // interface. You should be sure that the requested version is supported. // Requesting of unsupported interface version invokes undefined behavior. // - ICorProfilerInfo *v1() const noexcept; - ICorProfilerInfo2 *v2() const noexcept; - ICorProfilerInfo3 *v3() const noexcept; - ICorProfilerInfo4 *v4() const noexcept; - ICorProfilerInfo5 *v5() const noexcept; - ICorProfilerInfo6 *v6() const noexcept; - ICorProfilerInfo7 *v7() const noexcept; + ICorProfilerInfo *v1() const noexcept + { + _ASSERTE(m_version >= 1); + return m_pProfilerInfo; + } + + ICorProfilerInfo2 *v2() const noexcept + { + _ASSERTE(m_version >= 2); + return m_pProfilerInfo2; + } + + ICorProfilerInfo3 *v3() const noexcept + { + _ASSERTE(m_version >= 3); + return m_pProfilerInfo3; + } + + ICorProfilerInfo4 *v4() const noexcept + { + _ASSERTE(m_version >= 4); + return m_pProfilerInfo4; + } + + ICorProfilerInfo5 *v5() const noexcept + { + _ASSERTE(m_version >= 5); + return m_pProfilerInfo5; + } + + ICorProfilerInfo6 *v6() const noexcept + { + _ASSERTE(m_version >= 6); + return m_pProfilerInfo6; + } + + ICorProfilerInfo7 *v7() const noexcept + { + _ASSERTE(m_version >= 7); + return m_pProfilerInfo7; + } private: // Pointers to the implementation of the ProfilerInfo interface(s). diff --git a/src/trace/basetrace.cpp b/src/trace/basetrace.cpp deleted file mode 100644 index 2406a8e..0000000 --- a/src/trace/basetrace.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "profiler.h" -#include "profilerinfo.h" -#include "basetrace.h" - -BaseTrace::BaseTrace(Profiler &profiler) - : m_disabled(true) - , m_profiler(profiler) - , m_info(profiler.GetProfilerInfo()) -{ -} - -BaseTrace::~BaseTrace() -{ -} - -Log &BaseTrace::LOG() const noexcept -{ - return m_profiler.LOG(); -} - -ITraceLog &BaseTrace::TRACE() const noexcept -{ - return m_profiler.TRACE(); -} - -bool BaseTrace::IsEnabled() const noexcept -{ - return !m_disabled; -} diff --git a/src/trace/basetrace.h b/src/trace/basetrace.h index 8107977..c6eb391 100644 --- a/src/trace/basetrace.h +++ b/src/trace/basetrace.h @@ -5,6 +5,8 @@ class Profiler; class ProfilerInfo; +struct ProfilerConfig; + class Log; class ITraceLog; @@ -12,16 +14,20 @@ class ITraceLog; class BaseTrace { protected: - BaseTrace(Profiler &profiler); + inline BaseTrace(Profiler &profiler); - ~BaseTrace(); + ~BaseTrace() + {} - Log &LOG() const noexcept; + inline Log &LOG() const noexcept; - ITraceLog &TRACE() const noexcept; + inline ITraceLog &TRACE() const noexcept; public: - bool IsEnabled() const noexcept; + bool IsEnabled() const noexcept + { + return !m_disabled; + } protected: bool m_disabled; diff --git a/src/trace/commontrace.cpp b/src/trace/commontrace.cpp index eab3713..13684f5 100644 --- a/src/trace/commontrace.cpp +++ b/src/trace/commontrace.cpp @@ -11,10 +11,11 @@ #include -#include "profiler.h" #include "intervalsplitter.h" #include "commontrace.h" +#include "traceinlines.h" + #define CONTROL_SIGNAL_MIN (SIGRTMIN + 4) #define LOG_SIGNAL (CONTROL_SIGNAL_MIN + 0) #define LOG_SIGNAL_STOP (CONTROL_SIGNAL_MIN + 1) @@ -144,14 +145,14 @@ void CommonTrace::ProcessConfig(ProfilerConfig &config) // Line Tracing. // -//#if !defined(_TARGET_ARM_) && !defined(_TARGET_X86_) +#if 0 if (config.LineTraceEnabled) { config.LineTraceEnabled = false; LOG().Warn() << "Line tracing currently is not supported at this platform"; } -//#endif // _TARGET_ARM_ or _TARGET_X86_ +#endif // // Initializing thread local storage. @@ -831,11 +832,6 @@ __forceinline void CommonTrace::HandleSamplingPauseResume( } } -bool CommonTrace::IsSamplingSuspended() const noexcept -{ - return m_samplingSuspended; -} - HRESULT CommonTrace::AppDomainCreationFinished( AppDomainID appDomainId, HRESULT hrStatus) noexcept @@ -1163,27 +1159,3 @@ HRESULT CommonTrace::ThreadAssignedToOSThread( return hr; } - -auto CommonTrace::GetThreadStorage() -> - decltype(m_threadStorage.lock()) -{ - return m_threadStorage.lock(); -} - -auto CommonTrace::GetThreadStorage() const -> - decltype(m_threadStorage.lock_shared()) -{ - return m_threadStorage.lock_shared(); -} - -auto CommonTrace::GetClassStorage() -> - decltype(m_classStorage.lock()) -{ - return m_classStorage.lock(); -} - -auto CommonTrace::GetClassStorage() const -> - decltype(m_classStorage.lock_shared()) -{ - return m_classStorage.lock_shared(); -} diff --git a/src/trace/commontrace.h b/src/trace/commontrace.h index 9320e58..2c5934f 100644 --- a/src/trace/commontrace.h +++ b/src/trace/commontrace.h @@ -3,10 +3,6 @@ #include -#ifdef _TARGET_AMD64_ -#include -#endif // _TARGET_AMD64_ - #include #include #include @@ -72,23 +68,26 @@ private: ThreadInfo &thrInfo, void *context) noexcept; public: - ThreadInfo *GetThreadInfo() noexcept; + ThreadInfo *GetThreadInfo() noexcept; // TODO // Simple and safety version of GetThreadInfo() that can be used in signal // handlers. - ThreadInfo *GetThreadInfoR() const noexcept; + ThreadInfo *GetThreadInfoR() const noexcept; // TODO void InterruptSampling( SamplingSharedState &state, SamplingAction beforeAction = {}, SamplingAction action = {}, - SamplingAction afterAction = {}) noexcept; + SamplingAction afterAction = {}) noexcept; // TODO - void HandleSample(void *context) noexcept; + void HandleSample(void *context) noexcept; // TODO - void HandleSamplingPauseResume(bool shouldPause) noexcept; + void HandleSamplingPauseResume(bool shouldPause) noexcept; // TODO - bool IsSamplingSuspended() const noexcept; + bool IsSamplingSuspended() const noexcept + { + return m_samplingSuspended; + } HRESULT AppDomainCreationFinished( AppDomainID appDomainId, @@ -142,13 +141,25 @@ private: bool m_samplingSuspended; public: - auto GetThreadStorage() -> decltype(m_threadStorage.lock()); + auto GetThreadStorage() -> decltype(m_threadStorage.lock()) + { + return m_threadStorage.lock(); + } - auto GetThreadStorage() const -> decltype(m_threadStorage.lock_shared()); + auto GetThreadStorage() const -> decltype(m_threadStorage.lock_shared()) + { + return m_threadStorage.lock_shared(); + } - auto GetClassStorage() -> decltype(m_classStorage.lock()); + auto GetClassStorage() -> decltype(m_classStorage.lock()) + { + return m_classStorage.lock(); + } - auto GetClassStorage() const -> decltype(m_classStorage.lock_shared()); + auto GetClassStorage() const -> decltype(m_classStorage.lock_shared()) + { + return m_classStorage.lock_shared(); + } }; #endif // _COMMON_TRACE_H_ diff --git a/src/trace/cputrace.cpp b/src/trace/cputrace.cpp index b923961..00d5029 100644 --- a/src/trace/cputrace.cpp +++ b/src/trace/cputrace.cpp @@ -3,9 +3,10 @@ #include -#include "profiler.h" #include "cputrace.h" +#include "traceinlines.h" + CpuTrace::CpuTrace(Profiler &profiler) : BaseTrace(profiler) , m_logThread() diff --git a/src/trace/eventchannel.cpp b/src/trace/eventchannel.cpp deleted file mode 100644 index b13b903..0000000 --- a/src/trace/eventchannel.cpp +++ /dev/null @@ -1,210 +0,0 @@ -#include - -#include "eventchannel.h" - -#define EVENT_CHANNEL_START_CAP 128 // Should be power of 2. - -using Stack = EventSummary::Stack; - -EventSummary::EventSummary(Stack::size_type stackSize) - : ticks(0) - , count(0) - , matchPrefixSize(stackSize) - , stackSize(stackSize) - , ipIsChanged(false) - , ip(0) - , newFrames() -{} - -bool EventSummary::HasStackSample() const noexcept -{ - return count > 0 - || matchPrefixSize != stackSize - || ipIsChanged - || newFrames.size() > 0; -} - -bool EventSummary::HasAllocSample() const noexcept -{ - return allocTable.size() > 0; -} - -EventChannel::EventChannel() - : m_stack() - , m_currentState() - , m_buffer(EVENT_CHANNEL_START_CAP) - , m_mutex() - , m_bufferCapacityIncreaseIsPlanned(false) -{} - -void EventChannel::IncreaseBufferCapacity() -{ - m_buffer.reserve(m_buffer.capacity() * 2); - m_bufferCapacityIncreaseIsPlanned = false; -} - -bool EventChannel::EnsureBufferCapacity(ChanCanRealloc canRealloc) -{ - bool isBufferNoSpace = m_buffer.size() == m_buffer.capacity(); - Stack::difference_type needStackSize = - m_stack.size() - m_currentState.matchPrefixSize; - bool isStackNoSpace = m_currentState.newFrames.capacity() < needStackSize; - switch (canRealloc) - { - case ChanCanRealloc::NO: - if (isBufferNoSpace) - { - this->PlanToIncreaseBufferCapacity(); - } - assert(!isStackNoSpace); - return !isBufferNoSpace; - - case ChanCanRealloc::YES: - if (isBufferNoSpace || m_bufferCapacityIncreaseIsPlanned) - { - std::lock_guard lock(m_mutex); - this->IncreaseBufferCapacity(); - } - if (isStackNoSpace) - { - m_currentState.newFrames.reserve(needStackSize); - } - assert(m_buffer.capacity() != m_buffer.size()); - assert(m_currentState.newFrames.capacity() >= needStackSize); - return true; - } -} - -void EventChannel::Push(const FunctionInfo &funcInfo) noexcept -{ - assert(m_currentState.matchPrefixSize <= m_stack.size()); - m_stack.push_back(Frame{&funcInfo, 0}); - - // XXX: exception in this call will terminate process! - this->EnsureBufferCapacity(); // Perform planned reallocation. -} - -void EventChannel::Pop() noexcept -{ - assert(!m_stack.empty()); - assert(m_currentState.matchPrefixSize <= m_stack.size()); - m_stack.pop_back(); - if (m_stack.size() < m_currentState.matchPrefixSize) - { - m_currentState.matchPrefixSize = m_stack.size(); - m_currentState.ipIsChanged = false; - } - - // XXX: exception in this call will terminate process! - this->EnsureBufferCapacity(); // Perform planned reallocation. -} - -void EventChannel::ChIP(UINT_PTR ip, size_t idxFromTop) noexcept -{ - assert(idxFromTop < m_stack.size()); - assert(m_currentState.matchPrefixSize <= m_stack.size()); - assert( - m_stack.size() - idxFromTop >= m_currentState.matchPrefixSize - ); - - Frame &frame = const_cast(this->GetFrameFromTop(idxFromTop)); - size_t frameIdx = m_stack.size() - idxFromTop - 1; - assert(&m_stack[frameIdx] == &frame); - if (frame.ip != ip) - { - if (frameIdx + 1 == m_currentState.matchPrefixSize) - { - m_currentState.ipIsChanged = true; - } - frame.ip = ip; - } - - // XXX: exception in this call will terminate process! - this->EnsureBufferCapacity(); // Perform planned reallocation. -} - -void EventChannel::Allocation( - ClassInfo &classInfo, SIZE_T size, UINT_PTR ip) noexcept -{ - AllocInfo &allocInfo = - m_currentState.allocTable[classInfo.internalId.id][ip]; - allocInfo.allocCount++; - allocInfo.memSize += size; -} - -bool EventChannel::Sample( - DWORD ticks, ULONG count, ChanCanRealloc canRealloc) noexcept -{ - assert(m_currentState.matchPrefixSize <= m_stack.size()); - assert(m_currentState.matchPrefixSize <= m_currentState.stackSize); - assert(!m_currentState.ipIsChanged || m_currentState.matchPrefixSize > 0); - - // XXX: exception in this call will terminate process! - if (!this->EnsureBufferCapacity(canRealloc)) - { - // No space for new sample. - return false; - } - - m_currentState.ticks = ticks; - m_currentState.count = count; - - if (m_currentState.ipIsChanged) - { - m_currentState.ip = m_stack[m_currentState.matchPrefixSize - 1].ip; - } - - assert(m_currentState.newFrames.size() == 0); - m_currentState.newFrames.assign( - m_stack.cbegin() + m_currentState.matchPrefixSize, m_stack.cend()); - - m_buffer.push_back(std::move(m_currentState)); - m_currentState = EventSummary(m_stack.size()); - - return true; -} - -void EventChannel::PlanToIncreaseBufferCapacity() noexcept -{ - m_bufferCapacityIncreaseIsPlanned = true; -} - -Stack::size_type EventChannel::GetStackSize() const noexcept -{ - return m_stack.size(); -} - -bool EventChannel::HasStackSample() const noexcept -{ - return m_currentState.HasStackSample() || - m_stack.size() > m_currentState.matchPrefixSize; -} - -bool EventChannel::HasAllocSample() const noexcept -{ - return m_currentState.HasAllocSample(); -} - -const Frame &EventChannel::GetFrameFromTop( - Stack::size_type idxFromTop) const noexcept -{ - assert(idxFromTop < m_stack.size()); - return m_stack.rbegin()[idxFromTop]; -} - -size_t EventChannel::GetEventSummaryCount() const noexcept -{ - return m_buffer.size(); -} - -const EventSummary &EventChannel::GetCurrentEventSummary() noexcept -{ - m_mutex.lock(); - return m_buffer.front(); -} - -void EventChannel::NextEventSummary() noexcept -{ - m_buffer.pop_front(); - m_mutex.unlock(); -} diff --git a/src/trace/eventchannel.h b/src/trace/eventchannel.h index 12aadf4..d9cf755 100644 --- a/src/trace/eventchannel.h +++ b/src/trace/eventchannel.h @@ -7,10 +7,14 @@ #include #include +#include + #include "functioninfo.h" -#include "classinfo.h" +#include "objectinfo.h" #include "ringbuffer.h" +#define EVENT_CHANNEL_START_CAP 128 // Should be power of 2. + struct Frame { const FunctionInfo *pFuncInfo; @@ -23,13 +27,26 @@ struct AllocInfo SIZE_T memSize = 0; }; -typedef std::map> AllocTable; +typedef std::map AllocTable; + +typedef std::map> AllocIpTable; struct EventSummary { typedef std::vector Stack; - explicit EventSummary(Stack::size_type stackSize = 0); + explicit EventSummary(Stack::size_type stackSize = 0) + // Sample + : ticks(0) + , count(0) + , matchPrefixSize(stackSize) + , stackSize(stackSize) + , ipIsChanged(false) + , ip(0) + , newFrames() + // Allocations + , allocIpTable() + {} // // Sample @@ -48,15 +65,24 @@ struct EventSummary UINT_PTR ip; Stack newFrames; - bool HasStackSample() const noexcept; + bool HasStackSample() const noexcept + { + return count > 0 + || matchPrefixSize != stackSize + || ipIsChanged + || newFrames.size() > 0; + } // // Allocations // - AllocTable allocTable; + AllocIpTable allocIpTable; - bool HasAllocSample() const noexcept; + bool HasAllocSample() const noexcept + { + return allocIpTable.size() > 0; + } }; enum class ChanCanRealloc @@ -70,52 +96,197 @@ class EventChannel public: typedef EventSummary::Stack Stack; - EventChannel(); + EventChannel() + : m_stack() + , m_currentState() + , m_buffer(EVENT_CHANNEL_START_CAP) + , m_mutex() + , m_bufferCapacityIncreaseIsPlanned(false) + {} private: - void IncreaseBufferCapacity(); - - bool EnsureBufferCapacity(ChanCanRealloc canRealloc = ChanCanRealloc::YES); + void IncreaseBufferCapacity() + { + m_buffer.reserve(m_buffer.capacity() * 2); + m_bufferCapacityIncreaseIsPlanned = false; + } + + void PlanToIncreaseBufferCapacity() noexcept + { + m_bufferCapacityIncreaseIsPlanned = true; + } + + bool EnsureBufferCapacity(ChanCanRealloc canRealloc = ChanCanRealloc::YES) + { + bool isBufferNoSpace = m_buffer.size() == m_buffer.capacity(); + Stack::difference_type needStackSize = + m_stack.size() - m_currentState.matchPrefixSize; + bool isStackNoSpace = m_currentState.newFrames.capacity() < needStackSize; + switch (canRealloc) + { + case ChanCanRealloc::NO: + if (isBufferNoSpace) + { + this->PlanToIncreaseBufferCapacity(); + } + assert(!isStackNoSpace); + return !isBufferNoSpace; + + case ChanCanRealloc::YES: + if (isBufferNoSpace || m_bufferCapacityIncreaseIsPlanned) + { + std::lock_guard lock(m_mutex); + this->IncreaseBufferCapacity(); + } + if (isStackNoSpace) + { + m_currentState.newFrames.reserve(needStackSize); + } + assert(m_buffer.capacity() != m_buffer.size()); + assert(m_currentState.newFrames.capacity() >= needStackSize); + return true; + } + } public: // // Writer methods. // - void Push(const FunctionInfo &funcInfo) noexcept; - - void Pop() noexcept; - - void ChIP(UINT_PTR ip, size_t idxFromTop = 0) noexcept; - - void Allocation( - ClassInfo &classInfo, SIZE_T size, UINT_PTR ip = 0) noexcept; + void Push(const FunctionInfo &funcInfo) noexcept + { + assert(m_currentState.matchPrefixSize <= m_stack.size()); + m_stack.push_back(Frame{&funcInfo, 0}); + + // XXX: exception in this call will terminate process! + this->EnsureBufferCapacity(); // Perform planned reallocation. + } + + void Pop() noexcept + { + assert(!m_stack.empty()); + assert(m_currentState.matchPrefixSize <= m_stack.size()); + m_stack.pop_back(); + if (m_stack.size() < m_currentState.matchPrefixSize) + { + m_currentState.matchPrefixSize = m_stack.size(); + m_currentState.ipIsChanged = false; + } + + // XXX: exception in this call will terminate process! + this->EnsureBufferCapacity(); // Perform planned reallocation. + } + + void ChIP(UINT_PTR ip, size_t idxFromTop = 0) noexcept + { + assert(idxFromTop < m_stack.size()); + assert(m_currentState.matchPrefixSize <= m_stack.size()); + assert( + m_stack.size() - idxFromTop >= m_currentState.matchPrefixSize + ); + + Frame &frame = const_cast(this->GetFrameFromTop(idxFromTop)); + size_t frameIdx = m_stack.size() - idxFromTop - 1; + assert(&m_stack[frameIdx] == &frame); + if (frame.ip != ip) + { + if (frameIdx + 1 == m_currentState.matchPrefixSize) + { + m_currentState.ipIsChanged = true; + } + frame.ip = ip; + } + + // XXX: exception in this call will terminate process! + this->EnsureBufferCapacity(); // Perform planned reallocation. + } + + void Allocation(ObjectInfo &objInfo, UINT_PTR ip = 0) noexcept + { + _ASSERTE(objInfo.type != nullptr); + AllocInfo &allocInfo = + m_currentState.allocIpTable[objInfo.type->internalId.id][ip]; + allocInfo.allocCount++; + allocInfo.memSize += objInfo.size; + } bool Sample( DWORD ticks, ULONG count, - ChanCanRealloc canRealloc = ChanCanRealloc::YES) noexcept; - - void PlanToIncreaseBufferCapacity() noexcept; - - Stack::size_type GetStackSize() const noexcept; + ChanCanRealloc canRealloc = ChanCanRealloc::YES) noexcept + { + assert(m_currentState.matchPrefixSize <= m_stack.size()); + assert(m_currentState.matchPrefixSize <= m_currentState.stackSize); + assert(!m_currentState.ipIsChanged || m_currentState.matchPrefixSize > 0); + + // XXX: exception in this call will terminate process! + if (!this->EnsureBufferCapacity(canRealloc)) + { + // No space for new sample. + return false; + } + + m_currentState.ticks = ticks; + m_currentState.count = count; + + if (m_currentState.ipIsChanged) + { + m_currentState.ip = m_stack[m_currentState.matchPrefixSize - 1].ip; + } + + assert(m_currentState.newFrames.size() == 0); + m_currentState.newFrames.assign( + m_stack.cbegin() + m_currentState.matchPrefixSize, m_stack.cend()); + + m_buffer.push_back(std::move(m_currentState)); + m_currentState = EventSummary(m_stack.size()); + + return true; + } + + Stack::size_type GetStackSize() const noexcept + { + return m_stack.size(); + } const Frame &GetFrameFromTop( - Stack::size_type idxFromTop = 0) const noexcept; - - bool HasStackSample() const noexcept; - - bool HasAllocSample() const noexcept; + Stack::size_type idxFromTop = 0) const noexcept + { + assert(idxFromTop < m_stack.size()); + return m_stack.rbegin()[idxFromTop]; + } + + bool HasStackSample() const noexcept + { + return m_currentState.HasStackSample() || + m_stack.size() > m_currentState.matchPrefixSize; + } + + bool HasAllocSample() const noexcept + { + return m_currentState.HasAllocSample(); + } // // Reader methods. // - size_t GetEventSummaryCount() const noexcept; + size_t GetEventSummaryCount() const noexcept + { + return m_buffer.size(); + } // Reference only valid until next call to NextEventSummary(). - const EventSummary &GetCurrentEventSummary() noexcept; - - void NextEventSummary() noexcept; + const EventSummary &GetCurrentEventSummary() noexcept + { + m_mutex.lock(); + return m_buffer.front(); + } + + void NextEventSummary() noexcept + { + m_buffer.pop_front(); + m_mutex.unlock(); + } private: Stack m_stack; diff --git a/src/trace/executiontrace.cpp b/src/trace/executiontrace.cpp index 13d7743..700a9d2 100644 --- a/src/trace/executiontrace.cpp +++ b/src/trace/executiontrace.cpp @@ -1,9 +1,10 @@ #include #include -#include "profiler.h" #include "executiontrace.h" +#include "traceinlines.h" + EXTERN_C UINT_PTR __stdcall FunctionIDMapStub( FunctionID funcId, void *clientData, @@ -77,7 +78,8 @@ void ExecutionTrace::ProcessConfig(ProfilerConfig &config) // Check activation condition. // - if (config.ExecutionTraceEnabled || config.MemoryTraceEnabled) + if (config.ExecutionTraceEnabled || + (config.MemoryTraceEnabled && config.StackTrackingEnabled)) { m_disabled = false; } @@ -160,17 +162,6 @@ void ExecutionTrace::Shutdown() noexcept m_disabled = true; } -bool ExecutionTrace::IsPseudoFunction( - const FunctionInfo &funcInfo) const noexcept -{ - _ASSERTE(m_pUnmanagedFunctionInfo != nullptr); - _ASSERTE(m_pUnmanagedFunctionInfo->internalId.id == 0); - _ASSERTE(m_pJitFunctionInfo != nullptr); - _ASSERTE(m_pJitFunctionInfo->internalId.id == 1); - - return funcInfo.internalId.id >= 0 && funcInfo.internalId.id <= 1; -} - UINT_PTR ExecutionTrace::GetCurrentManagedIP( ThreadInfo &thrInfo, CONTEXT *winContext) noexcept { @@ -308,63 +299,6 @@ void ExecutionTrace::RestoreManagedIP( } } -bool ExecutionTrace::NeedSample( - ThreadInfo &thrInfo, SamplingSharedState &state) const noexcept -{ - if (m_disabled || !m_profiler.GetConfig().ExecutionTraceEnabled) - return false; - - return (thrInfo.fixTicks != state.genTicks) || - ( - thrInfo.eventChannel.HasStackSample() && - (m_profiler.GetConfig().CollectionMethod == - CollectionMethod::Instrumentation && - !m_profiler.GetCommonTrace().IsSamplingSuspended()) - ); -} - -HRESULT ContextToStackSnapshotContext( - const void *context, CONTEXT *winContext) noexcept; - -void ExecutionTrace::PrepareSample( - ThreadInfo &thrInfo, SamplingSharedState &state) noexcept -{ - if (m_profiler.GetConfig().LineTraceEnabled && - thrInfo.eventChannel.GetStackSize() > 0) - { - if (state.context) - { - CONTEXT winContext; - if (SUCCEEDED(ContextToStackSnapshotContext( - state.context, &winContext))) - { - this->RestoreManagedIP(thrInfo, &winContext); - } - else - { - thrInfo.eventChannel.ChIP(0); - } - } - else - { - this->RestoreManagedIP(thrInfo); - } - state.isIpRestored = true; - } -} - -void ExecutionTrace::AfterSample( - ThreadInfo &thrInfo, SamplingSharedState &state) noexcept -{ - if (state.isSampleSucceeds) - { - thrInfo.maxRestoreIpIdx = 0; - } -} - -HRESULT ContextToStackSnapshotContext( - const void *context, CONTEXT *winContext) noexcept; - void ExecutionTrace::UpdateCallStackPush(const FunctionInfo &funcInfo) noexcept { SamplingSharedState state = {}; @@ -495,7 +429,14 @@ HRESULT ExecutionTrace::JITStarted( HRESULT hr = S_OK; try { - this->UpdateCallStackPush(*m_pJitFunctionInfo); + if (m_profiler.GetConfig().LineTraceEnabled) + { + this->UpdateCallStackPush(*m_pJitFunctionInfo, 0); + } + else + { + this->UpdateCallStackPush(*m_pJitFunctionInfo); + } m_functionStorage.lock()->Place(functionId); } catch (const std::exception &e) @@ -639,7 +580,14 @@ HRESULT ExecutionTrace::ManagedToUnmanagedTransition( if (reason == COR_PRF_TRANSITION_CALL) { - this->UpdateCallStackPush(*m_pUnmanagedFunctionInfo); + if (m_profiler.GetConfig().LineTraceEnabled) + { + this->UpdateCallStackPush(*m_pUnmanagedFunctionInfo, 0); + } + else + { + this->UpdateCallStackPush(*m_pUnmanagedFunctionInfo); + } } return S_OK; diff --git a/src/trace/executiontrace.h b/src/trace/executiontrace.h index 1a69cda..6685078 100644 --- a/src/trace/executiontrace.h +++ b/src/trace/executiontrace.h @@ -8,6 +8,7 @@ #include #include "basetrace.h" +#include "commontrace.h" #include "sharedresource.h" #include "threadinfo.h" @@ -15,6 +16,9 @@ // #include "shared_iterator_range.h" +HRESULT ContextToStackSnapshotContext( + const void *context, CONTEXT *winContext) noexcept; + class ExecutionTrace final : public BaseTrace { private: @@ -27,7 +31,8 @@ public: void ProcessConfig(ProfilerConfig &config); - bool IsPseudoFunction(const FunctionInfo &funcInfo) const noexcept; + __forceinline bool IsPseudoFunction( + const FunctionInfo &funcInfo) const noexcept; void Shutdown() noexcept; @@ -37,13 +42,13 @@ public: void RestoreManagedIP( ThreadInfo &thrInfo, CONTEXT *winContext = nullptr) noexcept; - bool NeedSample( + __forceinline bool NeedSample( ThreadInfo &thrInfo, SamplingSharedState &state) const noexcept; - void PrepareSample( + __forceinline void PrepareSample( ThreadInfo &thrInfo, SamplingSharedState &state) noexcept; - void AfterSample( + __forceinline void AfterSample( ThreadInfo &thrInfo, SamplingSharedState &state) noexcept; private: diff --git a/src/trace/memorytrace.cpp b/src/trace/memorytrace.cpp index a8e7a1a..d370cd3 100644 --- a/src/trace/memorytrace.cpp +++ b/src/trace/memorytrace.cpp @@ -1,8 +1,14 @@ -#include "profiler.h" +#include + #include "memorytrace.h" +#include "traceinlines.h" + MemoryTrace::MemoryTrace(Profiler &profiler) : BaseTrace(profiler) + , m_objectTrackingSuspended(false) + , m_objectTrackingFailure(false) + , m_survivedObjects() { } @@ -41,10 +47,20 @@ void MemoryTrace::ProcessConfig(ProfilerConfig &config) ); } - // This events are common for memory tracing. - events = events - | COR_PRF_ENABLE_OBJECT_ALLOCATED - | COR_PRF_MONITOR_OBJECT_ALLOCATED; + if (config.CollectionMethod == CollectionMethod::Instrumentation || + config.CollectionMethod == CollectionMethod::Sampling) + { + // This events are required for tracking objects. + events = events + | COR_PRF_ENABLE_OBJECT_ALLOCATED + | COR_PRF_MONITOR_OBJECT_ALLOCATED; + } + + if (config.GcAllocTableTraceEnabled) + { + // This events are required for GC tracing. + events = events | COR_PRF_MONITOR_GC; + } // // Set Event Mask. @@ -63,20 +79,48 @@ void MemoryTrace::Shutdown() noexcept m_disabled = true; } -bool MemoryTrace::NeedSample( - ThreadInfo &thrInfo, SamplingSharedState &state) const noexcept +HRESULT MemoryTrace::InitAllocInfoByTypes(AllocTable &allocInfoByTypes) noexcept { - if (m_disabled) - return false; - - return thrInfo.eventChannel.HasAllocSample() && - ( - (thrInfo.fixTicks != state.genTicks) || - (m_profiler.GetConfig().CollectionMethod == - CollectionMethod::Instrumentation) || - (m_profiler.GetConfig().StackTrackingEnabled && - state.stackWillBeChanged) - ); + HRESULT hr = S_OK; + try + { + auto storage_lock = m_profiler.GetCommonTrace().GetClassStorage(); + for (const auto &objectWithClass : m_survivedObjects) + { + ObjectID objectId = objectWithClass.first; + ClassID classId = objectWithClass.second; + + ObjectInfo objInfo = {}; + objInfo.id = objectId; + // NOTE: it is OK to use classId == 0 here. + hr = objInfo.Initialize(m_profiler, *storage_lock, classId); + if (FAILED(hr)) + { + throw std::runtime_error( + "MemoryTrace::GetAllocInfoByTypes(): " + "Failed to initialize object info" + ); + } + + _ASSERTE(objInfo.type != nullptr); + if (!objInfo.type->isNamePrinted) + { + TRACE().DumpClassName(*objInfo.type); + objInfo.type->isNamePrinted = true; + } + + AllocInfo &allocInfo = + allocInfoByTypes[objInfo.type->internalId.id]; + allocInfo.allocCount++; + allocInfo.memSize += objInfo.size; + } + } + catch (const std::exception &e) + { + hr = m_profiler.HandleException(e); + } + + return hr; } HRESULT MemoryTrace::ObjectAllocated( @@ -92,37 +136,32 @@ HRESULT MemoryTrace::ObjectAllocated( HRESULT hr = S_OK; try { - auto storage_lock = m_profiler.GetCommonTrace().GetClassStorage(); - ClassInfo &classInfo = storage_lock->Place(classId).first; - classInfo.Initialize(m_profiler, *storage_lock); - if (!classInfo.isNamePrinted) - { - TRACE().DumpClassName(classInfo); - classInfo.isNamePrinted = true; - } - - SIZE_T objectSize = 0; - if (m_info.version() >= 4) + ObjectInfo objInfo = {}; + objInfo.id = objectId; { - hr = m_info.v4()->GetObjectSize2(objectId, &objectSize); + auto storage_lock = m_profiler.GetCommonTrace().GetClassStorage(); + hr = objInfo.Initialize(m_profiler, *storage_lock, classId); } - else + if (FAILED(hr)) { - ULONG size = 0; - hr = m_info.v1()->GetObjectSize(objectId, &size); - objectSize = size; + throw std::runtime_error( + "MemoryTrace::ObjectAllocated(): " + "Failed to initialize object info" + ); } - if (FAILED(hr)) + + _ASSERTE(objInfo.type != nullptr); + if (!objInfo.type->isNamePrinted) { - throw HresultException( - "MemoryTrace::ObjectAllocated(): GetObjectSize()", hr); + TRACE().DumpClassName(*objInfo.type); + objInfo.type->isNamePrinted = true; } UINT_PTR ip = 0; SamplingSharedState state = {}; m_profiler.GetCommonTrace().InterruptSampling( state, - [this, &classInfo, &objectSize, &ip] + [this, &objInfo, &ip] (ThreadInfo &thrInfo, SamplingSharedState &state) { EventChannel &channel = thrInfo.eventChannel; @@ -139,7 +178,7 @@ HRESULT MemoryTrace::ObjectAllocated( GetCurrentManagedIP(thrInfo); } } - channel.Allocation(classInfo, objectSize, ip); + channel.Allocation(objInfo, ip); } ); } @@ -150,3 +189,123 @@ HRESULT MemoryTrace::ObjectAllocated( return hr; } + +HRESULT MemoryTrace::GarbageCollectionStarted( + int cGenerations, + BOOL generationCollected[], + COR_PRF_GC_REASON reason) noexcept +{ + if (m_disabled) + return S_OK; + + _ASSERTE(m_survivedObjects.empty()); + + // Reset object tracking transaction members. + // Caching value of IsSamplingSuspended() for transaction. + m_objectTrackingSuspended = + m_profiler.GetCommonTrace().IsSamplingSuspended(); + m_objectTrackingFailure = false; + + return S_OK; +} + +HRESULT MemoryTrace::ObjectReferences( + ObjectID objectId, + ClassID classId, + ULONG cObjectRefs, + ObjectID objectRefIds[]) noexcept +{ + // Try to cancel callbacks if we don't need them. + + if (m_disabled) + return E_FAIL; + + if (m_objectTrackingSuspended) + return E_FAIL; + + if (m_objectTrackingFailure) + return E_FAIL; + + HRESULT hr = E_FAIL; + + // + // GC Allocations Table Tracing + // + + if (m_profiler.GetConfig().GcAllocTableTraceEnabled) + { + hr = S_OK; + try + { + // NOTE: it is unsafe to use objectId references here so we save + // them for handling in GarbageCollectionFinished() callback. + + auto objectWithClass = std::make_pair(objectId, classId); + // Ensure there is no copy of objectWithClass already in vector. + // NOTE: only in Debug build. + _ASSERTE( + std::find_if( + m_survivedObjects.begin(), + m_survivedObjects.end(), + [&objectWithClass] + (decltype(m_survivedObjects)::const_reference e) + { return e.first == objectWithClass.first; } + ) == m_survivedObjects.end() + ); + m_survivedObjects.push_back(objectWithClass); + } + catch (const std::exception &e) + { + // Can't update m_survivedObjects, so cancel transaction. + m_objectTrackingFailure = true; + m_survivedObjects.clear(); + hr = m_profiler.HandleException(e); + } + } + + return hr; +} + +HRESULT MemoryTrace::GarbageCollectionFinished() noexcept +{ + if (m_disabled) + return S_OK; + + // Check transaction is not suspended. + if (m_objectTrackingSuspended) + return S_OK; + + // Check transaction is vaid. + if (m_objectTrackingFailure) + return S_OK; + + HRESULT hr = S_OK; + + // + // GC Allocations Table Tracing + // + + if (m_profiler.GetConfig().GcAllocTableTraceEnabled) + { + try + { + DWORD ticks = m_profiler.GetTickCountFromInit(); + AllocTable allocInfoByTypes; + hr = this->InitAllocInfoByTypes(allocInfoByTypes); + if (SUCCEEDED(hr)) + { + // TODO: force other threads to do their memory samples + // in High Granularity mode. + TRACE().DumpGcHeapAllocTable(ticks, allocInfoByTypes); + } + } + catch (const std::exception &e) + { + hr = m_profiler.HandleException(e); + } + + m_survivedObjects.clear(); + } + + return hr; +} diff --git a/src/trace/memorytrace.h b/src/trace/memorytrace.h index 2582a58..d504f49 100644 --- a/src/trace/memorytrace.h +++ b/src/trace/memorytrace.h @@ -1,11 +1,15 @@ #ifndef _MEMORY_TRACE_H_ #define _MEMORY_TRACE_H_ +#include +#include + #include #include #include #include "basetrace.h" +#include "commontrace.h" class MemoryTrace : public BaseTrace { @@ -21,7 +25,7 @@ public: void Shutdown() noexcept; - bool NeedSample( + __forceinline bool NeedSample( ThreadInfo &thrInfo, SamplingSharedState &state) const noexcept; // void PrepareSample( @@ -31,6 +35,7 @@ public: // ThreadInfo &thrInfo, SamplingSharedState &state) noexcept; private: + HRESULT InitAllocInfoByTypes(AllocTable &allocInfoByTypes) noexcept; public: // @@ -40,6 +45,24 @@ public: HRESULT ObjectAllocated( ObjectID objectId, ClassID classId) noexcept; + + HRESULT GarbageCollectionStarted( + int cGenerations, + BOOL generationCollected[], + COR_PRF_GC_REASON reason) noexcept; + + HRESULT ObjectReferences( + ObjectID objectId, + ClassID classId, + ULONG cObjectRefs, + ObjectID objectRefIds[]) noexcept; + + HRESULT GarbageCollectionFinished() noexcept; + +private: + bool m_objectTrackingSuspended; + bool m_objectTrackingFailure; + std::vector> m_survivedObjects; }; #endif // _MEMORY_TRACE_H_ diff --git a/src/trace/traceinlines.h b/src/trace/traceinlines.h new file mode 100644 index 0000000..81bbb2d --- /dev/null +++ b/src/trace/traceinlines.h @@ -0,0 +1,107 @@ +#include "profiler.h" + +// +// BaseTrace +// + +BaseTrace::BaseTrace(Profiler &profiler) + : m_disabled(true) + , m_profiler(profiler) + , m_info(profiler.GetProfilerInfo()) +{} + +Log &BaseTrace::LOG() const noexcept +{ + return m_profiler.LOG(); +} + +ITraceLog &BaseTrace::TRACE() const noexcept +{ + return m_profiler.TRACE(); +} + +// +// ExecutionTrace +// + +bool ExecutionTrace::IsPseudoFunction( + const FunctionInfo &funcInfo) const noexcept +{ + _ASSERTE(m_pUnmanagedFunctionInfo != nullptr); + _ASSERTE(m_pUnmanagedFunctionInfo->internalId.id == 0); + _ASSERTE(m_pJitFunctionInfo != nullptr); + _ASSERTE(m_pJitFunctionInfo->internalId.id == 1); + + return funcInfo.internalId.id >= 0 && funcInfo.internalId.id <= 1; +} + +bool ExecutionTrace::NeedSample( + ThreadInfo &thrInfo, SamplingSharedState &state) const noexcept +{ + if (m_disabled || !m_profiler.GetConfig().ExecutionTraceEnabled) + return false; + + return (thrInfo.fixTicks != state.genTicks) || + ( + thrInfo.eventChannel.HasStackSample() && + (m_profiler.GetConfig().CollectionMethod == + CollectionMethod::Instrumentation && + !m_profiler.GetCommonTrace().IsSamplingSuspended()) + ); +} + +void ExecutionTrace::PrepareSample( + ThreadInfo &thrInfo, SamplingSharedState &state) noexcept +{ + if (m_profiler.GetConfig().LineTraceEnabled && + thrInfo.eventChannel.GetStackSize() > 0) + { + if (state.context) + { + CONTEXT winContext; + if (SUCCEEDED(ContextToStackSnapshotContext( + state.context, &winContext))) + { + this->RestoreManagedIP(thrInfo, &winContext); + } + else + { + thrInfo.eventChannel.ChIP(0); + } + } + else + { + this->RestoreManagedIP(thrInfo); + } + state.isIpRestored = true; + } +} + +void ExecutionTrace::AfterSample( + ThreadInfo &thrInfo, SamplingSharedState &state) noexcept +{ + if (state.isSampleSucceeds) + { + thrInfo.maxRestoreIpIdx = 0; + } +} + +// +// MemoryTrace +// + +bool MemoryTrace::NeedSample( + ThreadInfo &thrInfo, SamplingSharedState &state) const noexcept +{ + if (m_disabled) + return false; + + return thrInfo.eventChannel.HasAllocSample() && + ( + (thrInfo.fixTicks != state.genTicks) || + (m_profiler.GetConfig().CollectionMethod == + CollectionMethod::Instrumentation) || + (m_profiler.GetConfig().StackTrackingEnabled && + state.stackWillBeChanged) + ); +} diff --git a/src/tracelog.cpp b/src/tracelog.cpp index 1c45ec2..105250f 100644 --- a/src/tracelog.cpp +++ b/src/tracelog.cpp @@ -311,15 +311,15 @@ public: PAL_fprintf( m_pStream, "sam mem 0x%08x %d", threadIid.id, summary.ticks ); - for (const auto &classIdIpAllocInfo : summary.allocTable) + for (const auto &classIidIpAllocInfo : summary.allocIpTable) { - for (const auto &IpAllocInfo : classIdIpAllocInfo.second) + for (const auto &IpAllocInfo : classIidIpAllocInfo.second) { PAL_fprintf( m_pStream, IpAllocInfo.first != 0 ? " 0x%x:%Iu:%Iu:%p" : " 0x%x:%Iu:%Iu", - classIdIpAllocInfo.first, + classIidIpAllocInfo.first, IpAllocInfo.second.allocCount, IpAllocInfo.second.memSize, IpAllocInfo.first @@ -330,6 +330,26 @@ public: } } + virtual void DumpGcHeapAllocTable( + DWORD ticksFromStart, + const AllocTable &allocInfoByTypes) override + { + std::lock_guard streamLock(m_mStream); + + PAL_fprintf(m_pStream, "gch alt %d", ticksFromStart); + for (const auto &classIidAllocInfo : allocInfoByTypes) + { + PAL_fprintf( + m_pStream, + " 0x%x:%Iu:%Iu", + classIidAllocInfo.first, + classIidAllocInfo.second.allocCount, + classIidAllocInfo.second.memSize + ); + } + PAL_fprintf(m_pStream, "\n"); + } + private: PAL_FILE *m_pStream; std::mutex m_mStream; diff --git a/src/tracelog.h b/src/tracelog.h index 079882c..20a4c5e 100644 --- a/src/tracelog.h +++ b/src/tracelog.h @@ -120,6 +120,10 @@ public: virtual void DumpSample( InternalID threadIid, const EventSummary &summary) = 0; + + virtual void DumpGcHeapAllocTable( + DWORD ticksFromStart, + const AllocTable &allocInfoByTypes) = 0; }; #endif // _TRACE_LOG_H_