mirror of
https://github.com/yse/easy_profiler.git
synced 2025-01-14 00:27:55 +08:00
Trying to fix problem with c++11 magic statics (there is no support for visual studio 2013 and earlier, gcc < 4.3 and clang < 2.9);
Wrapped "final" keyword for different compilers support; Block descriptors now stored in unordered_map to make it easy to control theirs visibility level and to make it safe to unload dll/so during application execution.
This commit is contained in:
parent
79e503983c
commit
30de452113
130
include/profiler/easy_compiler_support.h
Normal file
130
include/profiler/easy_compiler_support.h
Normal file
@ -0,0 +1,130 @@
|
||||
/************************************************************************
|
||||
* file name : easy_compiler_support.h
|
||||
* ----------------- :
|
||||
* creation time : 2016/09/22
|
||||
* authors : Victor Zarubkin, Sergey Yagovtsev
|
||||
* emails : v.s.zarubkin@gmail.com, yse.sey@gmail.com
|
||||
* ----------------- :
|
||||
* description : This file contains auxiliary profiler macros for different compiler support.
|
||||
* ----------------- :
|
||||
* license : Lightweight profiler library for c++
|
||||
* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin
|
||||
* :
|
||||
* : This program is free software : you can redistribute it and / or modify
|
||||
* : it under the terms of the GNU General Public License as published by
|
||||
* : the Free Software Foundation, either version 3 of the License, or
|
||||
* : (at your option) any later version.
|
||||
* :
|
||||
* : This program is distributed in the hope that it will be useful,
|
||||
* : but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* : GNU General Public License for more details.
|
||||
* :
|
||||
* : You should have received a copy of the GNU General Public License
|
||||
* : along with this program.If not, see <http://www.gnu.org/licenses/>.
|
||||
************************************************************************/
|
||||
|
||||
#ifndef EASY_PROFILER__COMPILER_SUPPORT__H_______
|
||||
#define EASY_PROFILER__COMPILER_SUPPORT__H_______
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
//#define EASY_CODE_WRAP(Code) Code
|
||||
|
||||
#ifdef _WIN32
|
||||
# define __func__ __FUNCTION__
|
||||
# ifdef _BUILD_PROFILER
|
||||
# define PROFILER_API __declspec(dllexport)
|
||||
# else
|
||||
# define PROFILER_API __declspec(dllimport)
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if defined (_MSC_VER)
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Visual Studio
|
||||
|
||||
# if _MSC_VER <= 1800
|
||||
// There is no support for C++11 thread_local keyword prior to Visual Studio 2015. Use __declspec(thread) instead.
|
||||
// There is also no support for C++11 magic statics feature :( So it becomes slightly harder to initialize static vars - additional "if" for each profiler block.
|
||||
# define EASY_THREAD_LOCAL __declspec(thread)
|
||||
# define EASY_LOCAL_STATIC_PTR(VarType, VarName, VarInitializer)\
|
||||
__declspec(thread) static VarType VarName = 0;\
|
||||
if (!VarName)\
|
||||
VarName = VarInitializer
|
||||
# endif
|
||||
|
||||
#elif defined (__clang__)
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Clang Compiler
|
||||
|
||||
# if (__clang_major__ == 3 && __clang_minor__ < 3) || (__clang_major__ < 3)
|
||||
// There is no support for C++11 thread_local keyword prior to clang 3.3. Use __thread instead.
|
||||
# define EASY_THREAD_LOCAL __thread
|
||||
# endif
|
||||
|
||||
# if (__clang_major__ == 2 && __clang_minor__ < 9) || (__clang_major__ < 2)
|
||||
// There is no support for C++11 magic statics feature prior to clang 2.9. It becomes slightly harder to initialize static vars - additional "if" for each profiler block.
|
||||
# define EASY_LOCAL_STATIC_PTR(VarType, VarName, VarInitializer)\
|
||||
EASY_THREAD_LOCAL static VarType VarName = 0;\
|
||||
if (!VarName)\
|
||||
VarName = VarInitializer
|
||||
|
||||
// There is no support for C++11 final keyword prior to clang 2.9
|
||||
# define EASY_FINAL
|
||||
# endif
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// GNU Compiler
|
||||
|
||||
# if (__GNUC__ == 4 && __GNUC_MINOR__ < 8) || (__GNUC__ < 4)
|
||||
// There is no support for C++11 thread_local keyword prior to gcc 4.8. Use __thread instead.
|
||||
# define EASY_THREAD_LOCAL __thread
|
||||
# endif
|
||||
|
||||
# if (__GNUC__ == 4 && __GNUC_MINOR__ < 3) || (__GNUC__ < 4)
|
||||
// There is no support for C++11 magic statics feature prior to gcc 4.3. It becomes slightly harder to initialize static vars - additional "if" for each profiler block.
|
||||
# define EASY_LOCAL_STATIC_PTR(VarType, VarName, VarInitializer)\
|
||||
EASY_THREAD_LOCAL static VarType VarName = 0;\
|
||||
if (!VarName)\
|
||||
VarName = VarInitializer
|
||||
# endif
|
||||
|
||||
# if (__GNUC__ == 4 && __GNUC_MINOR__ < 7) || (__GNUC__ < 4)
|
||||
// There is no support for C++11 final keyword prior to gcc 4.7
|
||||
# define EASY_FINAL
|
||||
# endif
|
||||
|
||||
#endif
|
||||
// END // TODO: Add other compilers support
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Default values
|
||||
|
||||
#ifndef EASY_THREAD_LOCAL
|
||||
# define EASY_THREAD_LOCAL thread_local
|
||||
# define EASY_THREAD_LOCAL_CPP11
|
||||
#endif
|
||||
|
||||
#ifndef EASY_LOCAL_STATIC_PTR
|
||||
# define EASY_LOCAL_STATIC_PTR(VarType, VarName, VarInitializer) static VarType VarName = VarInitializer
|
||||
# define EASY_MAGIC_STATIC_CPP11
|
||||
#endif
|
||||
|
||||
#ifndef EASY_FINAL
|
||||
# define EASY_FINAL final
|
||||
#endif
|
||||
|
||||
#ifndef PROFILER_API
|
||||
# define PROFILER_API
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#endif // EASY_PROFILER__COMPILER_SUPPORT__H_______
|
@ -68,7 +68,7 @@ Block will be automatically completed by destructor.
|
||||
\ingroup profiler
|
||||
*/
|
||||
# define EASY_BLOCK(name, ...)\
|
||||
static const ::profiler::BlockDescRef EASY_UNIQUE_DESC(__LINE__)(::profiler::registerDescription(::profiler::extract_enable_flag(__VA_ARGS__),\
|
||||
EASY_LOCAL_STATIC_PTR(const ::profiler::BaseBlockDescriptor*, EASY_UNIQUE_DESC(__LINE__), ::profiler::registerDescription(::profiler::extract_enable_flag(__VA_ARGS__),\
|
||||
EASY_UNIQUE_LINE_ID, EASY_COMPILETIME_NAME(name), __FILE__, __LINE__, ::profiler::BLOCK_TYPE_BLOCK, ::profiler::extract_color(__VA_ARGS__)));\
|
||||
::profiler::Block EASY_UNIQUE_BLOCK(__LINE__)(EASY_UNIQUE_DESC(__LINE__), EASY_RUNTIME_NAME(name));\
|
||||
::profiler::beginBlock(EASY_UNIQUE_BLOCK(__LINE__)); // this is to avoid compiler warning about unused variable
|
||||
@ -98,7 +98,7 @@ Name of the block automatically created with function name.
|
||||
\ingroup profiler
|
||||
*/
|
||||
# define EASY_FUNCTION(...)\
|
||||
static const ::profiler::BlockDescRef EASY_UNIQUE_DESC(__LINE__)(::profiler::registerDescription(::profiler::extract_enable_flag(__VA_ARGS__),\
|
||||
EASY_LOCAL_STATIC_PTR(const ::profiler::BaseBlockDescriptor*, EASY_UNIQUE_DESC(__LINE__), ::profiler::registerDescription(::profiler::extract_enable_flag(__VA_ARGS__),\
|
||||
EASY_UNIQUE_LINE_ID, __func__, __FILE__, __LINE__, ::profiler::BLOCK_TYPE_BLOCK, ::profiler::extract_color(__VA_ARGS__)));\
|
||||
::profiler::Block EASY_UNIQUE_BLOCK(__LINE__)(EASY_UNIQUE_DESC(__LINE__), "");\
|
||||
::profiler::beginBlock(EASY_UNIQUE_BLOCK(__LINE__)); // this is to avoid compiler warning about unused variable
|
||||
@ -138,8 +138,8 @@ will end previously opened EASY_BLOCK or EASY_FUNCTION.
|
||||
\ingroup profiler
|
||||
*/
|
||||
# define EASY_EVENT(name, ...)\
|
||||
static const ::profiler::BlockDescRef EASY_UNIQUE_DESC(__LINE__)(\
|
||||
::profiler::registerDescription(::profiler::extract_enable_flag(__VA_ARGS__), EASY_UNIQUE_LINE_ID, EASY_COMPILETIME_NAME(name),\
|
||||
EASY_LOCAL_STATIC_PTR(const ::profiler::BaseBlockDescriptor*, EASY_UNIQUE_DESC(__LINE__), ::profiler::registerDescription(\
|
||||
::profiler::extract_enable_flag(__VA_ARGS__), EASY_UNIQUE_LINE_ID, EASY_COMPILETIME_NAME(name),\
|
||||
__FILE__, __LINE__, ::profiler::BLOCK_TYPE_EVENT, ::profiler::extract_color(__VA_ARGS__)));\
|
||||
::profiler::storeEvent(EASY_UNIQUE_DESC(__LINE__), EASY_RUNTIME_NAME(name));
|
||||
|
||||
@ -162,9 +162,9 @@ will end previously opened EASY_BLOCK or EASY_FUNCTION.
|
||||
\ingroup profiler
|
||||
*/
|
||||
# define EASY_THREAD(name)\
|
||||
EASY_THREAD_LOCAL static const char* EASY_TOKEN_CONCATENATE(unique_profiler_thread_name, __LINE__) = nullptr;\
|
||||
EASY_THREAD_LOCAL static const char* EASY_TOKEN_CONCATENATE(unique_profiler_thread_name, __LINE__) = 0;\
|
||||
::profiler::ThreadGuard EASY_TOKEN_CONCATENATE(unique_profiler_thread_guard, __LINE__);\
|
||||
if (EASY_TOKEN_CONCATENATE(unique_profiler_thread_name, __LINE__) == nullptr)\
|
||||
if (!EASY_TOKEN_CONCATENATE(unique_profiler_thread_name, __LINE__))\
|
||||
EASY_TOKEN_CONCATENATE(unique_profiler_thread_name, __LINE__) = ::profiler::registerThread(name,\
|
||||
EASY_TOKEN_CONCATENATE(unique_profiler_thread_guard, __LINE__));
|
||||
|
||||
@ -385,35 +385,7 @@ namespace profiler {
|
||||
|
||||
//***********************************************
|
||||
|
||||
class PROFILER_API BlockDescriptor final : public BaseBlockDescriptor
|
||||
{
|
||||
friend ::ProfileManager;
|
||||
|
||||
const char* m_name; ///< Static name of all blocks of the same type (blocks can have dynamic name) which is, in pair with descriptor id, a unique block identifier
|
||||
const char* m_filename; ///< Source file name where this block is declared
|
||||
EasyBlockStatus* m_pStatus; ///< Pointer to the enable flag in unordered_map
|
||||
uint16_t m_size; ///< Used memory size
|
||||
bool m_expired; ///< Is this descriptor expired
|
||||
|
||||
BlockDescriptor(EasyBlockStatus _status, const char* _name, const char* _filename, int _line, block_type_t _block_type, color_t _color);
|
||||
|
||||
public:
|
||||
|
||||
BlockDescriptor(block_id_t _id, EasyBlockStatus _status, const char* _name, const char* _filename, int _line, block_type_t _block_type, color_t _color);
|
||||
|
||||
inline const char* name() const {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
inline const char* file() const {
|
||||
return m_filename;
|
||||
}
|
||||
|
||||
}; // END of class BlockDescriptor.
|
||||
|
||||
//***********************************************
|
||||
|
||||
class PROFILER_API Block final : public BaseBlockData
|
||||
class PROFILER_API Block EASY_FINAL : public BaseBlockData
|
||||
{
|
||||
friend ::ProfileManager;
|
||||
friend ::ThreadStorage;
|
||||
@ -434,7 +406,7 @@ namespace profiler {
|
||||
public:
|
||||
|
||||
Block(Block&& that);
|
||||
Block(const BaseBlockDescriptor& _desc, const char* _runtimeName);
|
||||
Block(const BaseBlockDescriptor* _desc, const char* _runtimeName);
|
||||
Block(timestamp_t _begin_time, block_id_t _id, const char* _runtimeName);
|
||||
~Block();
|
||||
|
||||
@ -449,26 +421,7 @@ namespace profiler {
|
||||
|
||||
//***********************************************
|
||||
|
||||
class PROFILER_API BlockDescRef final
|
||||
{
|
||||
const BaseBlockDescriptor& m_desc;
|
||||
|
||||
public:
|
||||
|
||||
explicit BlockDescRef(const BaseBlockDescriptor& _desc) : m_desc(_desc) { }
|
||||
explicit BlockDescRef(const BaseBlockDescriptor* _desc) : m_desc(*_desc) { }
|
||||
inline operator const BaseBlockDescriptor& () const { return m_desc; }
|
||||
~BlockDescRef();
|
||||
|
||||
private:
|
||||
|
||||
BlockDescRef() = delete;
|
||||
BlockDescRef(const BlockDescRef&) = delete;
|
||||
BlockDescRef& operator = (const BlockDescRef&) = delete;
|
||||
|
||||
}; // END of class BlockDescRef.
|
||||
|
||||
class PROFILER_API ThreadGuard final {
|
||||
class PROFILER_API ThreadGuard EASY_FINAL {
|
||||
friend ::ProfileManager;
|
||||
thread_id_t m_id = 0;
|
||||
public:
|
||||
@ -499,7 +452,7 @@ namespace profiler {
|
||||
|
||||
\ingroup profiler
|
||||
*/
|
||||
PROFILER_API void storeEvent(const BaseBlockDescriptor& _desc, const char* _runtimeName);
|
||||
PROFILER_API void storeEvent(const BaseBlockDescriptor* _desc, const char* _runtimeName);
|
||||
|
||||
/** Begins block.
|
||||
|
||||
|
@ -32,52 +32,11 @@
|
||||
#define EASY_PROFILER__AUX__H_______
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cstddef>
|
||||
#include "profiler/easy_compiler_support.h"
|
||||
#include "profiler/profiler_colors.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
# define __func__ __FUNCTION__
|
||||
# if defined(_MSC_VER) && _MSC_VER <= 1800
|
||||
// There is no support for C++11 thread_local keyword prior to Visual Studio 2015. Use __declspec(thread) instead.
|
||||
# define EASY_THREAD_LOCAL __declspec(thread)
|
||||
# endif
|
||||
# ifdef _BUILD_PROFILER
|
||||
# define PROFILER_API __declspec(dllexport)
|
||||
# else
|
||||
# define PROFILER_API __declspec(dllimport)
|
||||
# endif
|
||||
|
||||
#elif defined (__clang__)
|
||||
|
||||
# if (__clang_major__ == 3 && __clang_minor__ < 3) || (__clang_major__ < 3)
|
||||
# define EASY_THREAD_LOCAL __thread
|
||||
# endif
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
|
||||
# if (__GNUC__ == 4 && __GNUC_MINOR__ < 8) || (__GNUC__ < 4)
|
||||
// There is no support for C++11 thread_local keyword prior to gcc 4.8. Use __thread instead.
|
||||
# define EASY_THREAD_LOCAL __thread
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
||||
// TODO: Check thread_local support for clang earlier than 3.3
|
||||
|
||||
#ifndef EASY_THREAD_LOCAL
|
||||
# define EASY_THREAD_LOCAL thread_local
|
||||
# define EASY_THREAD_LOCAL_CPP11
|
||||
#endif
|
||||
|
||||
#ifndef PROFILER_API
|
||||
# define PROFILER_API
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace profiler {
|
||||
|
||||
enum EasyBlockStatus : uint8_t {
|
||||
@ -89,7 +48,7 @@ namespace profiler {
|
||||
FORCE_ON_WITHOUT_CHILDREN = FORCE_ON | OFF_RECURSIVE, ///< The block is ALWAYS ON but all of it's children are OFF.
|
||||
};
|
||||
|
||||
struct passthrough_hash final {
|
||||
struct passthrough_hash EASY_FINAL {
|
||||
template <class T> inline size_t operator () (T _value) const {
|
||||
return static_cast<size_t>(_value);
|
||||
}
|
||||
@ -112,12 +71,12 @@ namespace profiler {
|
||||
|
||||
namespace profiler {
|
||||
|
||||
template <const bool IS_REF> struct NameSwitch final {
|
||||
template <const bool IS_REF> struct NameSwitch EASY_FINAL {
|
||||
static const char* runtime_name(const char* name) { return name; }
|
||||
static const char* compiletime_name(const char*, const char* autoGeneratedName) { return autoGeneratedName; }
|
||||
};
|
||||
|
||||
template <> struct NameSwitch<true> final {
|
||||
template <> struct NameSwitch<true> EASY_FINAL {
|
||||
static const char* runtime_name(const char*) { return ""; }
|
||||
static const char* compiletime_name(const char* name, const char*) { return name; }
|
||||
};
|
||||
|
@ -36,7 +36,7 @@ namespace profiler {
|
||||
typedef uint32_t block_index_t;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct BlockStatistics final
|
||||
struct BlockStatistics EASY_FINAL
|
||||
{
|
||||
::profiler::timestamp_t total_duration; ///< Summary duration of all block calls
|
||||
::profiler::timestamp_t min_duration; ///< Cached block->duration() value. TODO: Remove this if memory consumption will be too high
|
||||
@ -71,7 +71,7 @@ namespace profiler {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class BlocksTree final
|
||||
class BlocksTree EASY_FINAL
|
||||
{
|
||||
typedef BlocksTree This;
|
||||
|
||||
@ -80,12 +80,12 @@ namespace profiler {
|
||||
typedef ::std::vector<This> blocks_t;
|
||||
typedef ::std::vector<::profiler::block_index_t> children_t;
|
||||
|
||||
children_t children; ///< List of children blocks. May be empty.
|
||||
::profiler::SerializedBlock* node; ///< Pointer to serilized data (type, name, begin, end etc.)
|
||||
::profiler::BlockStatistics* per_parent_stats; ///< Pointer to statistics for this block within the parent (may be nullptr for top-level blocks)
|
||||
::profiler::BlockStatistics* per_frame_stats; ///< Pointer to statistics for this block within the frame (may be nullptr for top-level blocks)
|
||||
::profiler::BlockStatistics* per_thread_stats; ///< Pointer to statistics for this block within the bounds of all frames per current thread
|
||||
uint16_t depth; ///< Maximum number of sublevels (maximum children depth)
|
||||
children_t children; ///< List of children blocks. May be empty.
|
||||
::profiler::SerializedBlock* node; ///< Pointer to serilized data (type, name, begin, end etc.)
|
||||
::profiler::BlockStatistics* per_parent_stats; ///< Pointer to statistics for this block within the parent (may be nullptr for top-level blocks)
|
||||
::profiler::BlockStatistics* per_frame_stats; ///< Pointer to statistics for this block within the frame (may be nullptr for top-level blocks)
|
||||
::profiler::BlockStatistics* per_thread_stats; ///< Pointer to statistics for this block within the bounds of all frames per current thread
|
||||
uint16_t depth; ///< Maximum number of sublevels (maximum children depth)
|
||||
|
||||
BlocksTree()
|
||||
: node(nullptr)
|
||||
@ -170,7 +170,7 @@ namespace profiler {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class BlocksTreeRoot final
|
||||
class BlocksTreeRoot EASY_FINAL
|
||||
{
|
||||
typedef BlocksTreeRoot This;
|
||||
|
||||
@ -235,7 +235,7 @@ namespace profiler {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class PROFILER_API SerializedData final
|
||||
class PROFILER_API SerializedData EASY_FINAL
|
||||
{
|
||||
char* m_data;
|
||||
size_t m_size;
|
||||
|
@ -25,7 +25,7 @@ namespace profiler {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class PROFILER_API SerializedBlock final : public BaseBlockData
|
||||
class PROFILER_API SerializedBlock EASY_FINAL : public BaseBlockData
|
||||
{
|
||||
friend ::ProfileManager;
|
||||
friend ::ThreadStorage;
|
||||
@ -48,7 +48,7 @@ namespace profiler {
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma pack(push, 1)
|
||||
class PROFILER_API SerializedBlockDescriptor final : public BaseBlockDescriptor
|
||||
class PROFILER_API SerializedBlockDescriptor EASY_FINAL : public BaseBlockDescriptor
|
||||
{
|
||||
uint16_t m_nameLength;
|
||||
|
||||
|
@ -148,7 +148,7 @@ inline ::profiler::color_t textColorForRgb(::profiler::color_t _color)
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct EasyBlockItem final
|
||||
struct EasyBlockItem Q_DECL_FINAL
|
||||
{
|
||||
//const ::profiler::BlocksTree* block; ///< Pointer to profiler block
|
||||
qreal x; ///< x coordinate of the item (this is made qreal=double to avoid mistakes on very wide scene)
|
||||
@ -170,7 +170,7 @@ struct EasyBlockItem final
|
||||
|
||||
}; // END of struct EasyBlockItem.
|
||||
|
||||
struct EasyBlock final
|
||||
struct EasyBlock Q_DECL_FINAL
|
||||
{
|
||||
::profiler::BlocksTree tree;
|
||||
uint32_t tree_item;
|
||||
@ -202,7 +202,7 @@ typedef ::std::vector<EasyBlock> EasyBlocks;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct EasySelectedBlock final
|
||||
struct EasySelectedBlock Q_DECL_FINAL
|
||||
{
|
||||
const ::profiler::BlocksTreeRoot* root;
|
||||
::profiler::block_index_t tree;
|
||||
|
@ -86,7 +86,7 @@ namespace profiler_gui {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct EasyGlobals final
|
||||
struct EasyGlobals Q_DECL_FINAL
|
||||
{
|
||||
static EasyGlobals& instance();
|
||||
|
||||
|
@ -36,7 +36,7 @@
|
||||
|
||||
namespace profiler_gui {
|
||||
|
||||
class EasyGlobalSignals final : public QObject
|
||||
class EasyGlobalSignals Q_DECL_FINAL : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -58,7 +58,7 @@ class QDockWidget;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class EasyFileReader final
|
||||
class EasyFileReader Q_DECL_FINAL
|
||||
{
|
||||
::profiler::SerializedData m_serializedBlocks; ///<
|
||||
::profiler::SerializedData m_serializedDescriptors; ///<
|
||||
|
@ -49,7 +49,7 @@ typedef ::std::unordered_map<::profiler::thread_id_t, EasyTreeWidgetItem*, ::pro
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class EasyTreeWidgetLoader final
|
||||
class EasyTreeWidgetLoader Q_DECL_FINAL
|
||||
{
|
||||
ThreadedItems m_topLevelItems; ///<
|
||||
Items m_items; ///<
|
||||
@ -82,7 +82,7 @@ public:
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <class T>
|
||||
struct FillTreeClass final
|
||||
struct FillTreeClass Q_DECL_FINAL
|
||||
{
|
||||
static void setTreeInternal1(T& _safelocker, Items& _items, ThreadedItems& _topLevelItems, ::profiler::timestamp_t& _beginTime, const unsigned int _blocksNumber, const ::profiler::thread_blocks_tree_t& _blocksTree, bool _colorizeRows);
|
||||
static void setTreeInternal2(T& _safelocker, Items& _items, ThreadedItems& _topLevelItems, const ::profiler::timestamp_t& _beginTime, const ::profiler_gui::TreeBlocks& _blocks, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict, bool _colorizeRows);
|
||||
@ -91,7 +91,7 @@ struct FillTreeClass final
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct StubLocker final
|
||||
struct StubLocker Q_DECL_FINAL
|
||||
{
|
||||
void setDone() {}
|
||||
bool interrupted() const { return false; }
|
||||
|
@ -44,10 +44,10 @@ Block::Block(Block&& that)
|
||||
m_end = that.m_end;
|
||||
}
|
||||
|
||||
Block::Block(const BaseBlockDescriptor& _descriptor, const char* _runtimeName)
|
||||
: BaseBlockData(1ULL, _descriptor.id())
|
||||
Block::Block(const BaseBlockDescriptor* _descriptor, const char* _runtimeName)
|
||||
: BaseBlockData(1ULL, _descriptor->id())
|
||||
, m_name(_runtimeName)
|
||||
, m_status(_descriptor.status())
|
||||
, m_status(_descriptor->status())
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -54,9 +54,12 @@
|
||||
|
||||
namespace profiler {
|
||||
|
||||
const decltype(EVENT_DESCRIPTOR::Opcode) SWITCH_CONTEXT_OPCODE = 36;
|
||||
const int RAW_TIMESTAMP_TIME_TYPE = 1;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct ProcessInfo final {
|
||||
struct ProcessInfo {
|
||||
std::string name;
|
||||
uint32_t id = 0;
|
||||
int8_t valid = 0;
|
||||
@ -67,7 +70,7 @@ namespace profiler {
|
||||
// CSwitch class
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa964744(v=vs.85).aspx
|
||||
// EventType = 36
|
||||
struct CSwitch final
|
||||
struct CSwitch
|
||||
{
|
||||
uint32_t NewThreadId;
|
||||
uint32_t OldThreadId;
|
||||
@ -89,14 +92,13 @@ namespace profiler {
|
||||
typedef ::std::unordered_map<uint32_t, ProcessInfo, ::profiler::do_not_calc_hash> process_info_map;
|
||||
|
||||
// Using static is safe because processTraceEvent() is called from one thread
|
||||
static process_info_map PROCESS_INFO_TABLE;
|
||||
static thread_process_info_map THREAD_PROCESS_INFO_TABLE = ([](){ thread_process_info_map initial; initial[0U] = nullptr; return ::std::move(initial); })();
|
||||
process_info_map PROCESS_INFO_TABLE;
|
||||
thread_process_info_map THREAD_PROCESS_INFO_TABLE = ([](){ thread_process_info_map initial; initial[0U] = nullptr; return ::std::move(initial); })();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void WINAPI processTraceEvent(PEVENT_RECORD _traceEvent)
|
||||
{
|
||||
static const decltype(_traceEvent->EventHeader.EventDescriptor.Opcode) SWITCH_CONTEXT_OPCODE = 36;
|
||||
if (_traceEvent->EventHeader.EventDescriptor.Opcode != SWITCH_CONTEXT_OPCODE)
|
||||
return;
|
||||
|
||||
@ -188,10 +190,21 @@ namespace profiler {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef EASY_MAGIC_STATIC_CPP11
|
||||
class EasyEventTracerInstance {
|
||||
friend EasyEventTracer;
|
||||
EasyEventTracer instance;
|
||||
} EASY_EVENT_TRACER;
|
||||
#endif
|
||||
|
||||
EasyEventTracer& EasyEventTracer::instance()
|
||||
{
|
||||
#ifndef EASY_MAGIC_STATIC_CPP11
|
||||
return EASY_EVENT_TRACER.instance;
|
||||
#else
|
||||
static EasyEventTracer tracer;
|
||||
return tracer;
|
||||
#endif
|
||||
}
|
||||
|
||||
EasyEventTracer::EasyEventTracer()
|
||||
@ -231,7 +244,7 @@ namespace profiler {
|
||||
|
||||
#if EASY_LOG_ENABLED != 0
|
||||
if (!success)
|
||||
::std::cerr << "Warning: EasyProfiler failed to set Debug privelege for the application. Some context switch events could not get process name.";
|
||||
::std::cerr << "Warning: EasyProfiler failed to set Debug privelege for the application. Some context switch events could not get process name.\n";
|
||||
#endif
|
||||
|
||||
return success;
|
||||
@ -282,40 +295,43 @@ namespace profiler {
|
||||
}
|
||||
|
||||
#if EASY_LOG_ENABLED != 0
|
||||
::std::cerr << "Error: EasyProfiler.ETW not launched: ERROR_ALREADY_EXISTS. To stop another session execute cmd: logman stop \"" << KERNEL_LOGGER_NAME << "\" -ets";
|
||||
::std::cerr << "Error: EasyProfiler.ETW not launched: ERROR_ALREADY_EXISTS. To stop another session execute cmd: logman stop \"" << KERNEL_LOGGER_NAME << "\" -ets\n";
|
||||
#endif
|
||||
return EVENT_TRACING_WAS_LAUNCHED_BY_SOMEBODY_ELSE;
|
||||
}
|
||||
|
||||
case ERROR_ACCESS_DENIED:
|
||||
#if EASY_LOG_ENABLED != 0
|
||||
::std::cerr << "Error: EasyProfiler.ETW not launched: ERROR_ACCESS_DENIED. Try to launch your application as Administrator.";
|
||||
::std::cerr << "Error: EasyProfiler.ETW not launched: ERROR_ACCESS_DENIED. Try to launch your application as Administrator.\n";
|
||||
#endif
|
||||
return EVENT_TRACING_NOT_ENOUGH_ACCESS_RIGHTS;
|
||||
|
||||
case ERROR_BAD_LENGTH:
|
||||
#if EASY_LOG_ENABLED != 0
|
||||
::std::cerr << "Error: EasyProfiler.ETW not launched: ERROR_BAD_LENGTH. It seems that your KERNEL_LOGGER_NAME differs from \"" << m_properties.sessionName << "\". Try to re-compile easy_profiler or contact EasyProfiler developers.";
|
||||
::std::cerr << "Error: EasyProfiler.ETW not launched: ERROR_BAD_LENGTH. It seems that your KERNEL_LOGGER_NAME differs from \"" << m_properties.sessionName << "\". Try to re-compile easy_profiler or contact EasyProfiler developers.\n";
|
||||
#endif
|
||||
return EVENT_TRACING_BAD_PROPERTIES_SIZE;
|
||||
}
|
||||
|
||||
#if EASY_LOG_ENABLED != 0
|
||||
::std::cerr << "Error: EasyProfiler.ETW not launched: StartTrace() returned " << startTraceResult;
|
||||
::std::cerr << "Error: EasyProfiler.ETW not launched: StartTrace() returned " << startTraceResult << ::std::endl;
|
||||
#endif
|
||||
return EVENT_TRACING_MISTERIOUS_ERROR;
|
||||
}
|
||||
|
||||
::profiler::EventTracingEnableStatus EasyEventTracer::enable(bool _force)
|
||||
{
|
||||
static const decltype(m_properties.base.Wnode.ClientContext) RAW_TIMESTAMP_TIME_TYPE = 1;
|
||||
|
||||
::profiler::guard_lock<::profiler::spin_lock> lock(m_spin);
|
||||
if (m_bEnabled)
|
||||
return EVENT_TRACING_LAUNCHED_SUCCESSFULLY;
|
||||
|
||||
// Trying to set debug privilege for current process
|
||||
// to be able to get other process information (process name)
|
||||
/*
|
||||
Trying to set debug privilege for current process
|
||||
to be able to get other process information (process name).
|
||||
|
||||
Also it seems that debug privelege lets you to launch
|
||||
event tracing without Administrator access rights.
|
||||
*/
|
||||
if (!m_bPrivilegeSet)
|
||||
m_bPrivilegeSet = setDebugPrivilege();
|
||||
|
||||
@ -329,6 +345,7 @@ namespace profiler {
|
||||
m_properties.base.EnableFlags = EVENT_TRACE_FLAG_CSWITCH;
|
||||
m_properties.base.LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
|
||||
|
||||
// Start event tracing
|
||||
auto res = startTrace(_force);
|
||||
if (res != EVENT_TRACING_LAUNCHED_SUCCESSFULLY)
|
||||
return res;
|
||||
@ -342,7 +359,7 @@ namespace profiler {
|
||||
if (m_openedHandle == INVALID_PROCESSTRACE_HANDLE)
|
||||
{
|
||||
#if EASY_LOG_ENABLED != 0
|
||||
::std::cerr << "Error: EasyProfiler.ETW not launched: OpenTrace() returned invalid handle.";
|
||||
::std::cerr << "Error: EasyProfiler.ETW not launched: OpenTrace() returned invalid handle.\n";
|
||||
#endif
|
||||
return EVENT_TRACING_OPEN_TRACE_ERROR;
|
||||
}
|
||||
|
@ -50,11 +50,14 @@
|
||||
|
||||
namespace profiler {
|
||||
|
||||
class EasyEventTracer final
|
||||
class EasyEventTracer EASY_FINAL
|
||||
{
|
||||
#ifndef EASY_MAGIC_STATIC_CPP11
|
||||
friend class EasyEventTracerInstance;
|
||||
#endif
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct Properties final {
|
||||
struct Properties {
|
||||
EVENT_TRACE_PROPERTIES base;
|
||||
char sessionName[sizeof(KERNEL_LOGGER_NAME)];
|
||||
};
|
||||
|
@ -116,7 +116,7 @@ namespace profiler {
|
||||
|
||||
\ingroup profiler
|
||||
*/
|
||||
class hashed_cstr final : public cstring
|
||||
class hashed_cstr : public cstring
|
||||
{
|
||||
typedef cstring Parent;
|
||||
|
||||
@ -180,7 +180,7 @@ namespace std {
|
||||
|
||||
namespace profiler {
|
||||
|
||||
class hashed_stdstring final
|
||||
class hashed_stdstring
|
||||
{
|
||||
::std::string m_str;
|
||||
size_t m_hash;
|
||||
|
@ -39,7 +39,7 @@
|
||||
|
||||
namespace profiler {
|
||||
|
||||
class OStream final
|
||||
class OStream
|
||||
{
|
||||
::std::stringstream m_stream;
|
||||
|
||||
|
@ -67,7 +67,7 @@ extern "C" {
|
||||
MANAGER.setEnabled(isEnable);
|
||||
}
|
||||
|
||||
PROFILER_API void storeEvent(const BaseBlockDescriptor& _desc, const char* _runtimeName)
|
||||
PROFILER_API void storeEvent(const BaseBlockDescriptor* _desc, const char* _runtimeName)
|
||||
{
|
||||
MANAGER.storeBlock(_desc, _runtimeName);
|
||||
}
|
||||
@ -145,37 +145,69 @@ BaseBlockDescriptor::BaseBlockDescriptor(block_id_t _id, EasyBlockStatus _status
|
||||
|
||||
}
|
||||
|
||||
BlockDescriptor::BlockDescriptor(block_id_t _id, EasyBlockStatus _status, const char* _name, const char* _filename, int _line, block_type_t _block_type, color_t _color)
|
||||
: BaseBlockDescriptor(_id, _status, _line, _block_type, _color)
|
||||
, m_name(_name)
|
||||
, m_filename(_filename)
|
||||
, m_pStatus(nullptr)
|
||||
, m_size(static_cast<uint16_t>(sizeof(profiler::SerializedBlockDescriptor) + strlen(_name) + strlen(_filename) + 2))
|
||||
, m_expired(false)
|
||||
{
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
BlockDescriptor::BlockDescriptor(EasyBlockStatus _status, const char* _name, const char* _filename, int _line, block_type_t _block_type, color_t _color)
|
||||
: BaseBlockDescriptor(0, _status, _line, _block_type, _color)
|
||||
, m_name(_name)
|
||||
, m_filename(_filename)
|
||||
, m_pStatus(nullptr)
|
||||
, m_size(static_cast<uint16_t>(sizeof(profiler::SerializedBlockDescriptor) + strlen(_name) + strlen(_filename) + 2))
|
||||
, m_expired(false)
|
||||
{
|
||||
}
|
||||
#ifndef EASY_BLOCK_DESC_FULL_COPY
|
||||
# define EASY_BLOCK_DESC_FULL_COPY 0
|
||||
#endif
|
||||
|
||||
BlockDescRef::~BlockDescRef()
|
||||
#if EASY_BLOCK_DESC_FULL_COPY == 0
|
||||
# define EASY_BLOCK_DESC_STRING const char*
|
||||
# define EASY_BLOCK_DESC_STRING_LEN(s) static_cast<uint16_t>(strlen(s) + 1)
|
||||
# define EASY_BLOCK_DESC_STRING_VAL(s) s
|
||||
#else
|
||||
# define EASY_BLOCK_DESC_STRING std::string
|
||||
# define EASY_BLOCK_DESC_STRING_LEN(s) static_cast<uint16_t>(s.size() + 1)
|
||||
# define EASY_BLOCK_DESC_STRING_VAL(s) s.c_str()
|
||||
#endif
|
||||
|
||||
class BlockDescriptor : public BaseBlockDescriptor
|
||||
{
|
||||
MANAGER.markExpired(m_desc.id());
|
||||
}
|
||||
friend ProfileManager;
|
||||
|
||||
EASY_BLOCK_DESC_STRING m_name; ///< Static name of all blocks of the same type (blocks can have dynamic name) which is, in pair with descriptor id, a unique block identifier
|
||||
EASY_BLOCK_DESC_STRING m_filename; ///< Source file name where this block is declared
|
||||
uint16_t m_size; ///< Used memory size
|
||||
|
||||
public:
|
||||
|
||||
BlockDescriptor(block_id_t _id, EasyBlockStatus _status, const char* _name, const char* _filename, int _line, block_type_t _block_type, color_t _color)
|
||||
: BaseBlockDescriptor(_id, _status, _line, _block_type, _color)
|
||||
, m_name(_name)
|
||||
, m_filename(_filename)
|
||||
, m_size(static_cast<uint16_t>(sizeof(profiler::SerializedBlockDescriptor) + strlen(_name) + strlen(_filename) + 2))
|
||||
{
|
||||
}
|
||||
|
||||
const char* name() const {
|
||||
return EASY_BLOCK_DESC_STRING_VAL(m_name);
|
||||
}
|
||||
|
||||
const char* filename() const {
|
||||
return EASY_BLOCK_DESC_STRING_VAL(m_filename);
|
||||
}
|
||||
|
||||
uint16_t nameSize() const {
|
||||
return EASY_BLOCK_DESC_STRING_LEN(m_name);
|
||||
}
|
||||
|
||||
uint16_t filenameSize() const {
|
||||
return EASY_BLOCK_DESC_STRING_LEN(m_filename);
|
||||
}
|
||||
|
||||
}; // END of class BlockDescriptor.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ThreadStorage::storeBlock(const profiler::Block& block)
|
||||
{
|
||||
#if EASY_MEASURE_STORAGE_EXPAND != 0
|
||||
static const auto desc = MANAGER.addBlockDescriptor(EASY_STORAGE_EXPAND_ENABLED ? profiler::ON : profiler::OFF, EASY_UNIQUE_LINE_ID, "EasyProfiler.ExpandStorage", __FILE__, __LINE__, profiler::BLOCK_TYPE_BLOCK, profiler::colors::White);
|
||||
EASY_LOCAL_STATIC_PTR(const BaseBlockDescriptor*, desc,\
|
||||
MANAGER.addBlockDescriptor(EASY_STORAGE_EXPAND_ENABLED ? profiler::ON : profiler::OFF, EASY_UNIQUE_LINE_ID, "EasyProfiler.ExpandStorage",\
|
||||
__FILE__, __LINE__, profiler::BLOCK_TYPE_BLOCK, profiler::colors::White));
|
||||
|
||||
EASY_THREAD_LOCAL static profiler::timestamp_t beginTime = 0ULL;
|
||||
EASY_THREAD_LOCAL static profiler::timestamp_t endTime = 0ULL;
|
||||
#endif
|
||||
|
||||
auto name_length = static_cast<uint16_t>(strlen(block.name()));
|
||||
@ -183,14 +215,13 @@ void ThreadStorage::storeBlock(const profiler::Block& block)
|
||||
|
||||
#if EASY_MEASURE_STORAGE_EXPAND != 0
|
||||
const bool expanded = (desc->m_status & profiler::ON) && blocks.closedList.need_expand(size);
|
||||
profiler::Block b(0ULL, desc->id(), "");
|
||||
if (expanded) b.start();
|
||||
if (expanded) beginTime = getCurrentTime();
|
||||
#endif
|
||||
|
||||
auto data = blocks.closedList.allocate(size);
|
||||
|
||||
#if EASY_MEASURE_STORAGE_EXPAND != 0
|
||||
if (expanded) b.finish();
|
||||
if (expanded) endTime = getCurrentTime();
|
||||
#endif
|
||||
|
||||
::new (data) SerializedBlock(block, name_length);
|
||||
@ -199,10 +230,12 @@ void ThreadStorage::storeBlock(const profiler::Block& block)
|
||||
#if EASY_MEASURE_STORAGE_EXPAND != 0
|
||||
if (expanded)
|
||||
{
|
||||
name_length = 0;
|
||||
profiler::Block b(beginTime, desc->id(), "");
|
||||
b.finish(endTime);
|
||||
|
||||
size = static_cast<uint16_t>(sizeof(BaseBlockData) + 1);
|
||||
data = blocks.closedList.allocate(size);
|
||||
::new (data) SerializedBlock(b, name_length);
|
||||
::new (data) SerializedBlock(b, 0);
|
||||
blocks.usedMemorySize += size;
|
||||
}
|
||||
#endif
|
||||
@ -258,21 +291,23 @@ ProfileManager::~ProfileManager()
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef EASY_MAGIC_STATIC_CPP11
|
||||
class ProfileManagerInstance {
|
||||
friend ProfileManager;
|
||||
ProfileManager instance;
|
||||
} PROFILE_MANAGER;
|
||||
#endif
|
||||
|
||||
ProfileManager& ProfileManager::instance()
|
||||
{
|
||||
#ifndef EASY_MAGIC_STATIC_CPP11
|
||||
return PROFILE_MANAGER.instance;
|
||||
#else
|
||||
///C++11 makes possible to create Singleton without any warry about thread-safeness
|
||||
///http://preshing.com/20130930/double-checked-locking-is-fixed-in-cpp11/
|
||||
static ProfileManager m_profileManager;
|
||||
return m_profileManager;
|
||||
}
|
||||
|
||||
void ProfileManager::markExpired(profiler::block_id_t _id)
|
||||
{
|
||||
// Mark block descriptor as expired (descriptor may become expired if it's .dll/.so have been unloaded during application execution).
|
||||
// We can not delete this descriptor now, because we need to send/write all collected data first.
|
||||
|
||||
guard_lock_t lock(m_storedSpin);
|
||||
m_descriptors[_id]->m_expired = true;
|
||||
static ProfileManager profileManager;
|
||||
return profileManager;
|
||||
#endif
|
||||
}
|
||||
|
||||
ThreadStorage& ProfileManager::threadStorage(profiler::thread_id_t _thread_id)
|
||||
@ -287,9 +322,32 @@ ThreadStorage* ProfileManager::_findThreadStorage(profiler::thread_id_t _thread_
|
||||
return it != m_threads.end() ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
void ProfileManager::storeBlock(const profiler::BaseBlockDescriptor& _desc, const char* _runtimeName)
|
||||
const BaseBlockDescriptor* ProfileManager::addBlockDescriptor(EasyBlockStatus _defaultStatus,
|
||||
const char* _autogenUniqueId,
|
||||
const char* _name,
|
||||
const char* _filename,
|
||||
int _line,
|
||||
block_type_t _block_type,
|
||||
color_t _color)
|
||||
{
|
||||
if (!m_isEnabled.load(std::memory_order_acquire) || !(_desc.m_status & profiler::ON))
|
||||
guard_lock_t lock(m_storedSpin);
|
||||
|
||||
descriptors_map_t::key_type key(_autogenUniqueId);
|
||||
auto it = m_descriptorsMap.find(key);
|
||||
if (it != m_descriptorsMap.end())
|
||||
return m_descriptors[it->second];
|
||||
|
||||
auto desc = new BlockDescriptor(static_cast<block_id_t>(m_descriptors.size()), _defaultStatus, _name, _filename, _line, _block_type, _color);
|
||||
m_usedMemorySize += desc->m_size;
|
||||
m_descriptors.emplace_back(desc);
|
||||
m_descriptorsMap.emplace(key, desc->id());
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
void ProfileManager::storeBlock(const profiler::BaseBlockDescriptor* _desc, const char* _runtimeName)
|
||||
{
|
||||
if (!m_isEnabled.load(std::memory_order_acquire) || !(_desc->m_status & profiler::ON))
|
||||
return;
|
||||
|
||||
if (THREAD_STORAGE == nullptr)
|
||||
@ -400,13 +458,21 @@ void ProfileManager::endContextSwitch(profiler::thread_id_t _thread_id, profiler
|
||||
|
||||
void ProfileManager::setEnabled(bool isEnable)
|
||||
{
|
||||
m_isEnabled.store(isEnable, std::memory_order_release);
|
||||
auto time = getCurrentTime();
|
||||
const bool prev = m_isEnabled.exchange(isEnable, std::memory_order_release);
|
||||
if (prev == isEnable)
|
||||
return;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (isEnable) {
|
||||
if (isEnable)
|
||||
{
|
||||
m_beginTime = time;
|
||||
if (m_isEventTracingEnabled.load(std::memory_order_acquire))
|
||||
EasyEventTracer::instance().enable(true);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
m_endTime = time;
|
||||
EasyEventTracer::instance().disable();
|
||||
}
|
||||
#endif
|
||||
@ -489,6 +555,10 @@ uint32_t ProfileManager::dumpBlocksToStream(profiler::OStream& _outputStream)
|
||||
_outputStream.write(0LL);
|
||||
#endif
|
||||
|
||||
// Write begin and end time
|
||||
_outputStream.write(m_beginTime);
|
||||
_outputStream.write(m_endTime);
|
||||
|
||||
// Write blocks number and used memory size
|
||||
_outputStream.write(blocks_number);
|
||||
_outputStream.write(usedMemorySize);
|
||||
@ -498,21 +568,15 @@ uint32_t ProfileManager::dumpBlocksToStream(profiler::OStream& _outputStream)
|
||||
// Write block descriptors
|
||||
for (const auto descriptor : m_descriptors)
|
||||
{
|
||||
if (descriptor == nullptr)
|
||||
{
|
||||
_outputStream.write<uint16_t>(0U);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto name_size = static_cast<uint16_t>(strlen(descriptor->name()) + 1);
|
||||
const auto filename_size = static_cast<uint16_t>(strlen(descriptor->file()) + 1);
|
||||
const auto name_size = descriptor->nameSize();
|
||||
const auto filename_size = descriptor->filenameSize();
|
||||
const auto size = static_cast<uint16_t>(sizeof(profiler::SerializedBlockDescriptor) + name_size + filename_size);
|
||||
|
||||
_outputStream.write(size);
|
||||
_outputStream.write<profiler::BaseBlockDescriptor>(*descriptor);
|
||||
_outputStream.write(name_size);
|
||||
_outputStream.write(descriptor->name(), name_size);
|
||||
_outputStream.write(descriptor->file(), filename_size);
|
||||
_outputStream.write(descriptor->filename(), filename_size);
|
||||
}
|
||||
|
||||
// Write blocks and context switch events for each thread
|
||||
@ -544,17 +608,6 @@ uint32_t ProfileManager::dumpBlocksToStream(profiler::OStream& _outputStream)
|
||||
++it;
|
||||
}
|
||||
|
||||
// Remove all expired block descriptors (descriptor may become expired if it's .dll/.so have been unloaded during application execution)
|
||||
for (auto& desc : m_descriptors)
|
||||
{
|
||||
if (desc == nullptr || !desc->m_expired)
|
||||
continue;
|
||||
|
||||
m_usedMemorySize -= desc->m_size;
|
||||
delete desc;
|
||||
desc = nullptr;
|
||||
}
|
||||
|
||||
//if (wasEnabled)
|
||||
// ::profiler::setEnabled(true);
|
||||
|
||||
@ -595,26 +648,17 @@ const char* ProfileManager::registerThread(const char* name, ThreadGuard& thread
|
||||
return THREAD_STORAGE->name.c_str();
|
||||
}
|
||||
|
||||
void ProfileManager::setBlockStatus(block_id_t _id, const hashed_stdstring& _key, EasyBlockStatus _status)
|
||||
void ProfileManager::setBlockStatus(block_id_t _id, EasyBlockStatus _status)
|
||||
{
|
||||
if (m_isEnabled.load(std::memory_order_acquire))
|
||||
return; // Changing blocks statuses is restricted while profile session is active
|
||||
|
||||
guard_lock_t lock(m_storedSpin);
|
||||
|
||||
auto desc = m_descriptors[_id];
|
||||
if (desc != nullptr)
|
||||
if (_id < m_descriptors.size())
|
||||
{
|
||||
auto desc = m_descriptors[_id];
|
||||
lock.unlock();
|
||||
|
||||
*desc->m_pStatus = _status;
|
||||
desc->m_status = _status; // TODO: possible concurrent access, atomic may be needed
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef _WIN32
|
||||
blocks_enable_status_t::key_type key(_key.c_str(), _key.size(), _key.hcode());
|
||||
m_blocksEnableStatus[key] = _status;
|
||||
#else
|
||||
m_blocksEnableStatus[_key] = _status;
|
||||
#endif
|
||||
desc->m_status = _status;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,8 +81,13 @@ namespace profiler {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define EASY_ENABLE_BLOCK_STATUS 1
|
||||
#define EASY_ENABLE_ALIGNMENT 0
|
||||
#ifndef EASY_ENABLE_BLOCK_STATUS
|
||||
# define EASY_ENABLE_BLOCK_STATUS 1
|
||||
#endif
|
||||
|
||||
#ifndef EASY_ENABLE_ALIGNMENT
|
||||
# define EASY_ENABLE_ALIGNMENT 0
|
||||
#endif
|
||||
|
||||
#if EASY_ENABLE_ALIGNMENT == 0
|
||||
# define EASY_ALIGNED(TYPE, VAR, A) TYPE VAR
|
||||
@ -101,7 +106,7 @@ namespace profiler {
|
||||
#endif
|
||||
|
||||
template <const uint16_t N>
|
||||
class chunk_allocator final
|
||||
class chunk_allocator
|
||||
{
|
||||
struct chunk { EASY_ALIGNED(int8_t, data[N], 64); chunk* prev = nullptr; };
|
||||
|
||||
@ -248,11 +253,11 @@ const uint16_t SIZEOF_CSWITCH = sizeof(profiler::BaseBlockData) + 1 + sizeof(uin
|
||||
typedef std::vector<profiler::SerializedBlock*> serialized_list_t;
|
||||
|
||||
template <class T, const uint16_t N>
|
||||
struct BlocksList final
|
||||
struct BlocksList
|
||||
{
|
||||
BlocksList() = default;
|
||||
|
||||
class Stack final {
|
||||
class Stack {
|
||||
//std::stack<T> m_stack;
|
||||
std::vector<T> m_stack;
|
||||
|
||||
@ -298,7 +303,7 @@ struct BlocksList final
|
||||
};
|
||||
|
||||
|
||||
struct ThreadStorage final
|
||||
struct ThreadStorage
|
||||
{
|
||||
BlocksList<std::reference_wrapper<profiler::Block>, SIZEOF_CSWITCH * (uint16_t)128U> blocks;
|
||||
BlocksList<profiler::Block, SIZEOF_CSWITCH * (uint16_t)128U> sync;
|
||||
@ -320,9 +325,13 @@ struct ThreadStorage final
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ProfileManager final
|
||||
class BlockDescriptor;
|
||||
|
||||
class ProfileManager
|
||||
{
|
||||
friend profiler::BlockDescRef;
|
||||
#ifndef EASY_MAGIC_STATIC_CPP11
|
||||
friend class ProfileManagerInstance;
|
||||
#endif
|
||||
|
||||
ProfileManager();
|
||||
ProfileManager(const ProfileManager& p) = delete;
|
||||
@ -330,21 +339,22 @@ class ProfileManager final
|
||||
|
||||
typedef profiler::guard_lock<profiler::spin_lock> guard_lock_t;
|
||||
typedef std::map<profiler::thread_id_t, ThreadStorage> map_of_threads_stacks;
|
||||
typedef std::vector<profiler::BlockDescriptor*> block_descriptors_t;
|
||||
typedef std::vector<BlockDescriptor*> block_descriptors_t;
|
||||
|
||||
#ifdef _WIN32
|
||||
typedef std::unordered_map<profiler::hashed_cstr, profiler::EasyBlockStatus> blocks_enable_status_t;
|
||||
typedef std::unordered_map<profiler::hashed_cstr, profiler::block_id_t> descriptors_map_t;
|
||||
#else
|
||||
typedef std::unordered_map<profiler::hashed_stdstring, profiler::EasyBlockStatus> blocks_enable_status_t;
|
||||
typedef std::unordered_map<profiler::hashed_stdstring, profiler::block_id_t> descriptors_map_t;
|
||||
#endif
|
||||
|
||||
map_of_threads_stacks m_threads;
|
||||
block_descriptors_t m_descriptors;
|
||||
blocks_enable_status_t m_blocksEnableStatus;
|
||||
descriptors_map_t m_descriptorsMap;
|
||||
uint64_t m_usedMemorySize = 0;
|
||||
profiler::timestamp_t m_beginTime = 0;
|
||||
profiler::timestamp_t m_endTime = 0;
|
||||
profiler::spin_lock m_spin;
|
||||
profiler::spin_lock m_storedSpin;
|
||||
profiler::block_id_t m_idCounter = 0;
|
||||
std::atomic_bool m_isEnabled;
|
||||
std::atomic_bool m_isEventTracingEnabled;
|
||||
|
||||
@ -353,7 +363,7 @@ class ProfileManager final
|
||||
#endif
|
||||
|
||||
uint32_t dumpBlocksToStream(profiler::OStream& _outputStream);
|
||||
void setBlockStatus(profiler::block_id_t _id, const profiler::hashed_stdstring& _key, profiler::EasyBlockStatus _status);
|
||||
void setBlockStatus(profiler::block_id_t _id, profiler::EasyBlockStatus _status);
|
||||
|
||||
std::thread m_listenThread;
|
||||
bool m_isAlreadyListened = false;
|
||||
@ -367,32 +377,15 @@ public:
|
||||
static ProfileManager& instance();
|
||||
~ProfileManager();
|
||||
|
||||
template <class ... TArgs>
|
||||
const profiler::BaseBlockDescriptor* addBlockDescriptor(profiler::EasyBlockStatus _defaultStatus, const char* _autogenUniqueId, TArgs ... _args)
|
||||
{
|
||||
auto desc = new profiler::BlockDescriptor(_defaultStatus, _args...);
|
||||
const profiler::BaseBlockDescriptor* addBlockDescriptor(profiler::EasyBlockStatus _defaultStatus,
|
||||
const char* _autogenUniqueId,
|
||||
const char* _name,
|
||||
const char* _filename,
|
||||
int _line,
|
||||
profiler::block_type_t _block_type,
|
||||
profiler::color_t _color);
|
||||
|
||||
guard_lock_t lock(m_storedSpin);
|
||||
m_usedMemorySize += desc->m_size;
|
||||
desc->m_id = m_idCounter++;
|
||||
m_descriptors.emplace_back(desc);
|
||||
|
||||
blocks_enable_status_t::key_type key(_autogenUniqueId);
|
||||
auto it = m_blocksEnableStatus.find(key);
|
||||
if (it != m_blocksEnableStatus.end())
|
||||
{
|
||||
desc->m_status = it->second;
|
||||
desc->m_pStatus = &it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
desc->m_pStatus = &m_blocksEnableStatus.emplace(key, desc->status()).first->second;
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
void storeBlock(const profiler::BaseBlockDescriptor& _desc, const char* _runtimeName);
|
||||
void storeBlock(const profiler::BaseBlockDescriptor* _desc, const char* _runtimeName);
|
||||
void beginBlock(profiler::Block& _block);
|
||||
void endBlock();
|
||||
void setEnabled(bool isEnable);
|
||||
@ -419,7 +412,6 @@ public:
|
||||
void stopListenSignalToCapture();
|
||||
private:
|
||||
|
||||
void markExpired(profiler::block_id_t _id);
|
||||
ThreadStorage& threadStorage(profiler::thread_id_t _thread_id);
|
||||
ThreadStorage* _findThreadStorage(profiler::thread_id_t _thread_id);
|
||||
|
||||
|
214
src/reader.cpp
214
src/reader.cpp
@ -217,6 +217,8 @@ void validate_pointers(::std::atomic<int>& _progress, const char* _oldbase, ::pr
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const int64_t TIME_FACTOR = 1000000000LL;
|
||||
|
||||
extern "C" PROFILER_API::profiler::block_index_t fillTreesFromFile(::std::atomic<int>& progress, const char* filename,
|
||||
::profiler::SerializedData& serialized_blocks,
|
||||
::profiler::SerializedData& serialized_descriptors,
|
||||
@ -236,6 +238,17 @@ extern "C" PROFILER_API::profiler::block_index_t fillTreesFromFile(::std::atomic
|
||||
int64_t cpu_frequency = 0LL;
|
||||
inFile.read((char*)&cpu_frequency, sizeof(int64_t));
|
||||
|
||||
::profiler::timestamp_t begin_time = 0, end_time = 0;
|
||||
inFile.read((char*)&begin_time, sizeof(::profiler::timestamp_t));
|
||||
inFile.read((char*)&end_time, sizeof(::profiler::timestamp_t));
|
||||
if (cpu_frequency != 0)
|
||||
{
|
||||
begin_time *= TIME_FACTOR;
|
||||
begin_time /= cpu_frequency;
|
||||
end_time *= TIME_FACTOR;
|
||||
end_time /= cpu_frequency;
|
||||
}
|
||||
|
||||
uint32_t total_blocks_number = 0;
|
||||
inFile.read((char*)&total_blocks_number, sizeof(decltype(total_blocks_number)));
|
||||
if (total_blocks_number == 0)
|
||||
@ -338,25 +351,29 @@ extern "C" PROFILER_API::profiler::block_index_t fillTreesFromFile(::std::atomic
|
||||
inFile.read(data, sz);
|
||||
i += sz;
|
||||
auto baseData = reinterpret_cast<::profiler::SerializedBlock*>(data);
|
||||
auto t_begin = reinterpret_cast<::profiler::timestamp_t*>(data);
|
||||
auto t_end = t_begin + 1;
|
||||
|
||||
if (cpu_frequency != 0)
|
||||
{
|
||||
auto t_begin = reinterpret_cast<::profiler::timestamp_t*>(data);
|
||||
auto t_end = t_begin + 1;
|
||||
|
||||
*t_begin *= 1000000000LL;
|
||||
*t_begin *= TIME_FACTOR;
|
||||
*t_begin /= cpu_frequency;
|
||||
|
||||
*t_end *= 1000000000LL;
|
||||
*t_end *= TIME_FACTOR;
|
||||
*t_end /= cpu_frequency;
|
||||
}
|
||||
|
||||
blocks.emplace_back();
|
||||
::profiler::BlocksTree& tree = blocks.back();
|
||||
tree.node = baseData;
|
||||
const auto block_index = blocks_counter++;
|
||||
if (*t_end > begin_time)
|
||||
{
|
||||
if (*t_begin < begin_time)
|
||||
*t_begin = begin_time;
|
||||
|
||||
root.sync.emplace_back(block_index);
|
||||
blocks.emplace_back();
|
||||
::profiler::BlocksTree& tree = blocks.back();
|
||||
tree.node = baseData;
|
||||
const auto block_index = blocks_counter++;
|
||||
|
||||
root.sync.emplace_back(block_index);
|
||||
}
|
||||
|
||||
if (progress.load(::std::memory_order_acquire) < 0)
|
||||
break; // Loading interrupted
|
||||
@ -388,111 +405,116 @@ extern "C" PROFILER_API::profiler::block_index_t fillTreesFromFile(::std::atomic
|
||||
if (descriptors[baseData->id()] == nullptr)
|
||||
return 0;
|
||||
|
||||
auto t_begin = reinterpret_cast<::profiler::timestamp_t*>(data);
|
||||
auto t_end = t_begin + 1;
|
||||
|
||||
if (cpu_frequency != 0)
|
||||
{
|
||||
auto t_begin = reinterpret_cast<::profiler::timestamp_t*>(data);
|
||||
auto t_end = t_begin + 1;
|
||||
|
||||
*t_begin *= 1000000000LL;
|
||||
*t_begin *= TIME_FACTOR;
|
||||
*t_begin /= cpu_frequency;
|
||||
|
||||
*t_end *= 1000000000LL;
|
||||
*t_end *= TIME_FACTOR;
|
||||
*t_end /= cpu_frequency;
|
||||
}
|
||||
|
||||
blocks.emplace_back();
|
||||
::profiler::BlocksTree& tree = blocks.back();
|
||||
tree.node = baseData;
|
||||
const auto block_index = blocks_counter++;
|
||||
|
||||
auto& per_parent_statistics = parent_statistics[thread_id];
|
||||
auto& per_thread_statistics = thread_statistics[thread_id];
|
||||
|
||||
if (*tree.node->name() != 0)
|
||||
if (*t_end >= begin_time)
|
||||
{
|
||||
// If block has runtime name then generate new id for such block.
|
||||
// Blocks with the same name will have same id.
|
||||
if (*t_begin < begin_time)
|
||||
*t_begin = begin_time;
|
||||
|
||||
IdMap::key_type key(tree.node->name());
|
||||
auto it = identification_table.find(key);
|
||||
if (it != identification_table.end())
|
||||
blocks.emplace_back();
|
||||
::profiler::BlocksTree& tree = blocks.back();
|
||||
tree.node = baseData;
|
||||
const auto block_index = blocks_counter++;
|
||||
|
||||
auto& per_parent_statistics = parent_statistics[thread_id];
|
||||
auto& per_thread_statistics = thread_statistics[thread_id];
|
||||
|
||||
if (*tree.node->name() != 0)
|
||||
{
|
||||
// There is already block with such name, use it's id
|
||||
baseData->setId(it->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
// There were no blocks with such name, generate new id and save it in the table for further usage.
|
||||
auto id = static_cast<::profiler::block_id_t>(descriptors.size());
|
||||
identification_table.emplace(key, id);
|
||||
if (descriptors.capacity() == descriptors.size())
|
||||
descriptors.reserve((descriptors.size() * 3) >> 1);
|
||||
descriptors.push_back(descriptors[baseData->id()]);
|
||||
baseData->setId(id);
|
||||
}
|
||||
}
|
||||
// If block has runtime name then generate new id for such block.
|
||||
// Blocks with the same name will have same id.
|
||||
|
||||
if (!root.children.empty())
|
||||
{
|
||||
auto& back = blocks[root.children.back()];
|
||||
auto t1 = back.node->end();
|
||||
auto mt0 = tree.node->begin();
|
||||
if (mt0 < t1)//parent - starts earlier than last ends
|
||||
{
|
||||
//auto lower = ::std::lower_bound(root.children.begin(), root.children.end(), tree);
|
||||
/**/
|
||||
EASY_BLOCK("Find children", ::profiler::colors::Blue);
|
||||
auto rlower1 = ++root.children.rbegin();
|
||||
for (; rlower1 != root.children.rend() && !(mt0 > blocks[*rlower1].node->begin()); ++rlower1);
|
||||
auto lower = rlower1.base();
|
||||
::std::move(lower, root.children.end(), ::std::back_inserter(tree.children));
|
||||
|
||||
root.children.erase(lower, root.children.end());
|
||||
EASY_END_BLOCK;
|
||||
|
||||
::profiler::timestamp_t children_duration = 0;
|
||||
if (gather_statistics)
|
||||
IdMap::key_type key(tree.node->name());
|
||||
auto it = identification_table.find(key);
|
||||
if (it != identification_table.end())
|
||||
{
|
||||
EASY_BLOCK("Gather statistic within parent", ::profiler::colors::Magenta);
|
||||
per_parent_statistics.clear();
|
||||
|
||||
//per_parent_statistics.reserve(tree.children.size()); // this gives slow-down on Windows
|
||||
//per_parent_statistics.reserve(tree.children.size() * 2); // this gives no speed-up on Windows
|
||||
// TODO: check this behavior on Linux
|
||||
|
||||
for (auto i : tree.children)
|
||||
{
|
||||
auto& child = blocks[i];
|
||||
child.per_parent_stats = update_statistics(per_parent_statistics, child, i, block_index);
|
||||
|
||||
children_duration += child.node->duration();
|
||||
if (tree.depth < child.depth)
|
||||
tree.depth = child.depth;
|
||||
}
|
||||
// There is already block with such name, use it's id
|
||||
baseData->setId(it->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto i : tree.children)
|
||||
{
|
||||
const auto& child = blocks[i];
|
||||
children_duration += child.node->duration();
|
||||
if (tree.depth < child.depth)
|
||||
tree.depth = child.depth;
|
||||
}
|
||||
// There were no blocks with such name, generate new id and save it in the table for further usage.
|
||||
auto id = static_cast<::profiler::block_id_t>(descriptors.size());
|
||||
identification_table.emplace(key, id);
|
||||
if (descriptors.capacity() == descriptors.size())
|
||||
descriptors.reserve((descriptors.size() * 3) >> 1);
|
||||
descriptors.push_back(descriptors[baseData->id()]);
|
||||
baseData->setId(id);
|
||||
}
|
||||
|
||||
++tree.depth;
|
||||
}
|
||||
}
|
||||
|
||||
root.children.emplace_back(block_index);// ::std::move(tree));
|
||||
if (!root.children.empty())
|
||||
{
|
||||
auto& back = blocks[root.children.back()];
|
||||
auto t1 = back.node->end();
|
||||
auto mt0 = tree.node->begin();
|
||||
if (mt0 < t1)//parent - starts earlier than last ends
|
||||
{
|
||||
//auto lower = ::std::lower_bound(root.children.begin(), root.children.end(), tree);
|
||||
/**/
|
||||
EASY_BLOCK("Find children", ::profiler::colors::Blue);
|
||||
auto rlower1 = ++root.children.rbegin();
|
||||
for (; rlower1 != root.children.rend() && !(mt0 > blocks[*rlower1].node->begin()); ++rlower1);
|
||||
auto lower = rlower1.base();
|
||||
::std::move(lower, root.children.end(), ::std::back_inserter(tree.children));
|
||||
|
||||
root.children.erase(lower, root.children.end());
|
||||
EASY_END_BLOCK;
|
||||
|
||||
::profiler::timestamp_t children_duration = 0;
|
||||
if (gather_statistics)
|
||||
{
|
||||
EASY_BLOCK("Gather statistic within parent", ::profiler::colors::Magenta);
|
||||
per_parent_statistics.clear();
|
||||
|
||||
//per_parent_statistics.reserve(tree.children.size()); // this gives slow-down on Windows
|
||||
//per_parent_statistics.reserve(tree.children.size() * 2); // this gives no speed-up on Windows
|
||||
// TODO: check this behavior on Linux
|
||||
|
||||
for (auto i : tree.children)
|
||||
{
|
||||
auto& child = blocks[i];
|
||||
child.per_parent_stats = update_statistics(per_parent_statistics, child, i, block_index);
|
||||
|
||||
children_duration += child.node->duration();
|
||||
if (tree.depth < child.depth)
|
||||
tree.depth = child.depth;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto i : tree.children)
|
||||
{
|
||||
const auto& child = blocks[i];
|
||||
children_duration += child.node->duration();
|
||||
if (tree.depth < child.depth)
|
||||
tree.depth = child.depth;
|
||||
}
|
||||
}
|
||||
|
||||
++tree.depth;
|
||||
}
|
||||
}
|
||||
|
||||
root.children.emplace_back(block_index);// ::std::move(tree));
|
||||
|
||||
|
||||
|
||||
if (gather_statistics)
|
||||
{
|
||||
EASY_BLOCK("Gather per thread statistics", ::profiler::colors::Coral);
|
||||
tree.per_thread_stats = update_statistics(per_thread_statistics, tree, block_index, thread_id);
|
||||
if (gather_statistics)
|
||||
{
|
||||
EASY_BLOCK("Gather per thread statistics", ::profiler::colors::Coral);
|
||||
tree.per_thread_stats = update_statistics(per_thread_statistics, tree, block_index, thread_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (progress.load(::std::memory_order_acquire) < 0)
|
||||
|
@ -32,7 +32,7 @@ namespace profiler {
|
||||
#if defined(_WIN32) && defined(EASY_USE_CRITICAL_SECTION)
|
||||
// std::atomic_flag on Windows works slower than critical section, so we will use it instead of std::atomic_flag...
|
||||
// By the way, Windows critical sections are slower than std::atomic_flag on Unix.
|
||||
class spin_lock final { CRITICAL_SECTION m_lock; public:
|
||||
class spin_lock { CRITICAL_SECTION m_lock; public:
|
||||
|
||||
void lock() {
|
||||
EnterCriticalSection(&m_lock);
|
||||
@ -52,7 +52,7 @@ namespace profiler {
|
||||
};
|
||||
#else
|
||||
// std::atomic_flag on Unix works fine and very fast (almost instant!)
|
||||
class spin_lock final { ::std::atomic_flag m_lock; public:
|
||||
class spin_lock { ::std::atomic_flag m_lock; public:
|
||||
|
||||
void lock() {
|
||||
while (m_lock.test_and_set(::std::memory_order_acquire));
|
||||
@ -69,7 +69,7 @@ namespace profiler {
|
||||
#endif
|
||||
|
||||
template <class T>
|
||||
class guard_lock final
|
||||
class guard_lock
|
||||
{
|
||||
T& m_lock;
|
||||
bool m_isLocked = false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user