mirror of
https://github.com/yse/easy_profiler.git
synced 2024-12-28 01:04:41 +08:00
c2b3a8f5dc
(Core) Added new API functions for getting current time (ticks) and converting it to nano- and microseconds.
1917 lines
65 KiB
C++
1917 lines
65 KiB
C++
/************************************************************************
|
|
* file name : profile_manager.cpp
|
|
* ----------------- :
|
|
* creation time : 2016/02/16
|
|
* authors : Sergey Yagovtsev, Victor Zarubkin
|
|
* emails : yse.sey@gmail.com, v.s.zarubkin@gmail.com
|
|
* ----------------- :
|
|
* description : The file contains implementation of Profile manager and implement access c-function
|
|
* :
|
|
* license : Lightweight profiler library for c++
|
|
* : Copyright(C) 2016-2017 Sergey Yagovtsev, Victor Zarubkin
|
|
* :
|
|
* : Licensed under either of
|
|
* : * MIT license (LICENSE.MIT or http://opensource.org/licenses/MIT)
|
|
* : * Apache License, Version 2.0, (LICENSE.APACHE or http://www.apache.org/licenses/LICENSE-2.0)
|
|
* : at your option.
|
|
* :
|
|
* : The MIT License
|
|
* :
|
|
* : Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* : of this software and associated documentation files (the "Software"), to deal
|
|
* : in the Software without restriction, including without limitation the rights
|
|
* : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
* : of the Software, and to permit persons to whom the Software is furnished
|
|
* : to do so, subject to the following conditions:
|
|
* :
|
|
* : The above copyright notice and this permission notice shall be included in all
|
|
* : copies or substantial portions of the Software.
|
|
* :
|
|
* : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
* : INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
* : PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
* : LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
* : TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
* : USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
* :
|
|
* : The Apache License, Version 2.0 (the "License")
|
|
* :
|
|
* : You may not use this file except in compliance with the License.
|
|
* : You may obtain a copy of the License at
|
|
* :
|
|
* : http://www.apache.org/licenses/LICENSE-2.0
|
|
* :
|
|
* : Unless required by applicable law or agreed to in writing, software
|
|
* : distributed under the License is distributed on an "AS IS" BASIS,
|
|
* : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* : See the License for the specific language governing permissions and
|
|
* : limitations under the License.
|
|
************************************************************************/
|
|
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <assert.h>
|
|
#include "profile_manager.h"
|
|
|
|
#include <easy/serialized_block.h>
|
|
#include <easy/easy_net.h>
|
|
#include <easy/easy_socket.h>
|
|
|
|
#include "event_trace_win.h"
|
|
#include "current_time.h"
|
|
|
|
#if EASY_OPTION_LOG_ENABLED != 0
|
|
# include <iostream>
|
|
|
|
# ifndef EASY_ERRORLOG
|
|
# define EASY_ERRORLOG ::std::cerr
|
|
# endif
|
|
|
|
# ifndef EASY_LOG
|
|
# define EASY_LOG ::std::cerr
|
|
# endif
|
|
|
|
# ifndef EASY_ERROR
|
|
# define EASY_ERROR(LOG_MSG) EASY_ERRORLOG << "EasyProfiler ERROR: " << LOG_MSG
|
|
# endif
|
|
|
|
# ifndef EASY_WARNING
|
|
# define EASY_WARNING(LOG_MSG) EASY_ERRORLOG << "EasyProfiler WARNING: " << LOG_MSG
|
|
# endif
|
|
|
|
# ifndef EASY_LOGMSG
|
|
# define EASY_LOGMSG(LOG_MSG) EASY_LOG << "EasyProfiler INFO: " << LOG_MSG
|
|
# endif
|
|
|
|
# ifndef EASY_LOG_ONLY
|
|
# define EASY_LOG_ONLY(CODE) CODE
|
|
# endif
|
|
|
|
#else
|
|
|
|
# ifndef EASY_ERROR
|
|
# define EASY_ERROR(LOG_MSG)
|
|
# endif
|
|
|
|
# ifndef EASY_WARNING
|
|
# define EASY_WARNING(LOG_MSG)
|
|
# endif
|
|
|
|
# ifndef EASY_LOGMSG
|
|
# define EASY_LOGMSG(LOG_MSG)
|
|
# endif
|
|
|
|
# ifndef EASY_LOG_ONLY
|
|
# define EASY_LOG_ONLY(CODE)
|
|
# endif
|
|
|
|
#endif
|
|
|
|
#ifdef min
|
|
#undef min
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
using namespace profiler;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#if !defined(EASY_PROFILER_VERSION_MAJOR) || !defined(EASY_PROFILER_VERSION_MINOR) || !defined(EASY_PROFILER_VERSION_PATCH)
|
|
# ifdef _WIN32
|
|
# error EASY_PROFILER_VERSION_MAJOR and EASY_PROFILER_VERSION_MINOR and EASY_PROFILER_VERSION_PATCH macros must be defined
|
|
# else
|
|
# error "EASY_PROFILER_VERSION_MAJOR and EASY_PROFILER_VERSION_MINOR and EASY_PROFILER_VERSION_PATCH macros must be defined"
|
|
# endif
|
|
#endif
|
|
|
|
# define EASY_PROFILER_PRODUCT_VERSION "v" EASY_STRINGIFICATION(EASY_PROFILER_VERSION_MAJOR) "." \
|
|
EASY_STRINGIFICATION(EASY_PROFILER_VERSION_MINOR) "." \
|
|
EASY_STRINGIFICATION(EASY_PROFILER_VERSION_PATCH)
|
|
|
|
# define EASY_VERSION_INT(v_major, v_minor, v_patch) ((static_cast<uint32_t>(v_major) << 24) | (static_cast<uint32_t>(v_minor) << 16) | static_cast<uint32_t>(v_patch))
|
|
extern const uint32_t PROFILER_SIGNATURE = ('E' << 24) | ('a' << 16) | ('s' << 8) | 'y';
|
|
extern const uint32_t EASY_CURRENT_VERSION = EASY_VERSION_INT(EASY_PROFILER_VERSION_MAJOR, EASY_PROFILER_VERSION_MINOR, EASY_PROFILER_VERSION_PATCH);
|
|
# undef EASY_VERSION_INT
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
# define EASY_PROF_DISABLED 0
|
|
# define EASY_PROF_ENABLED 1
|
|
# define EASY_PROF_DUMP 2
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//auto& MANAGER = ProfileManager::instance();
|
|
# define MANAGER ProfileManager::instance()
|
|
const uint8_t FORCE_ON_FLAG = profiler::FORCE_ON & ~profiler::ON;
|
|
|
|
#ifdef _WIN32
|
|
decltype(LARGE_INTEGER::QuadPart) const CPU_FREQUENCY = ([](){ LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); return freq.QuadPart; })();
|
|
# define TICKS_TO_US(ticks) ticks * 1000000LL / CPU_FREQUENCY
|
|
#elif defined(USE_STD_CHRONO)
|
|
# define TICKS_TO_US(ticks) ticks / 1000
|
|
#else
|
|
int64_t calculate_cpu_frequency()
|
|
{
|
|
double g_TicksPerNanoSec;
|
|
struct timespec begints, endts;
|
|
uint64_t begin = 0, end = 0;
|
|
clock_gettime(CLOCK_MONOTONIC, &begints);
|
|
begin = getCurrentTime();
|
|
volatile uint64_t i;
|
|
for (i = 0; i < 100000000; i++); /* must be CPU intensive */
|
|
end = getCurrentTime();
|
|
clock_gettime(CLOCK_MONOTONIC, &endts);
|
|
struct timespec tmpts;
|
|
const int NANO_SECONDS_IN_SEC = 1000000000;
|
|
tmpts.tv_sec = endts.tv_sec - begints.tv_sec;
|
|
tmpts.tv_nsec = endts.tv_nsec - begints.tv_nsec;
|
|
if (tmpts.tv_nsec < 0)
|
|
{
|
|
tmpts.tv_sec--;
|
|
tmpts.tv_nsec += NANO_SECONDS_IN_SEC;
|
|
}
|
|
|
|
uint64_t nsecElapsed = tmpts.tv_sec * 1000000000LL + tmpts.tv_nsec;
|
|
g_TicksPerNanoSec = (double)(end - begin) / (double)nsecElapsed;
|
|
|
|
int64_t cpu_frequency = int(g_TicksPerNanoSec * 1000000);
|
|
|
|
return cpu_frequency;
|
|
}
|
|
|
|
static std::atomic<int64_t> CPU_FREQUENCY = ATOMIC_VAR_INIT(1);
|
|
# define TICKS_TO_US(ticks) ticks * 1000 / CPU_FREQUENCY.load(std::memory_order_acquire)
|
|
#endif
|
|
|
|
extern const profiler::color_t EASY_COLOR_INTERNAL_EVENT = 0xffffffff; // profiler::colors::White
|
|
const profiler::color_t EASY_COLOR_THREAD_END = 0xff212121; // profiler::colors::Dark
|
|
const profiler::color_t EASY_COLOR_START = 0xff4caf50; // profiler::colors::Green
|
|
const profiler::color_t EASY_COLOR_END = 0xfff44336; // profiler::colors::Red
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
EASY_THREAD_LOCAL static ::ThreadStorage* THIS_THREAD = nullptr;
|
|
EASY_THREAD_LOCAL static int32_t THIS_THREAD_STACK_SIZE = 0;
|
|
EASY_THREAD_LOCAL static profiler::timestamp_t THIS_THREAD_FRAME_T = 0ULL;
|
|
EASY_THREAD_LOCAL static bool THIS_THREAD_FRAME = false;
|
|
EASY_THREAD_LOCAL static bool THIS_THREAD_HALT = false;
|
|
EASY_THREAD_LOCAL static bool THIS_THREAD_IS_MAIN = false;
|
|
|
|
EASY_THREAD_LOCAL static profiler::timestamp_t THIS_THREAD_FRAME_T_MAX = 0ULL;
|
|
EASY_THREAD_LOCAL static profiler::timestamp_t THIS_THREAD_FRAME_T_CUR = 0ULL;
|
|
EASY_THREAD_LOCAL static profiler::timestamp_t THIS_THREAD_FRAME_T_ACC = 0ULL;
|
|
EASY_THREAD_LOCAL static uint32_t THIS_THREAD_N_FRAMES = 0;
|
|
EASY_THREAD_LOCAL static bool THIS_THREAD_FRAME_T_RESET_MAX = false;
|
|
EASY_THREAD_LOCAL static bool THIS_THREAD_FRAME_T_RESET_AVG = false;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef BUILD_WITH_EASY_PROFILER
|
|
# define EASY_EVENT_RES(res, name, ...)\
|
|
EASY_LOCAL_STATIC_PTR(const ::profiler::BaseBlockDescriptor*, EASY_UNIQUE_DESC(__LINE__), MANAGER.addBlockDescriptor(\
|
|
::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__)));\
|
|
res = MANAGER.storeBlock(EASY_UNIQUE_DESC(__LINE__), EASY_RUNTIME_NAME(name))
|
|
|
|
# define EASY_FORCE_EVENT(timestamp, name, ...)\
|
|
EASY_LOCAL_STATIC_PTR(const ::profiler::BaseBlockDescriptor*, EASY_UNIQUE_DESC(__LINE__), addBlockDescriptor(\
|
|
::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__)));\
|
|
storeBlockForce(EASY_UNIQUE_DESC(__LINE__), EASY_RUNTIME_NAME(name), timestamp)
|
|
|
|
# define EASY_FORCE_EVENT2(timestamp, name, ...)\
|
|
EASY_LOCAL_STATIC_PTR(const ::profiler::BaseBlockDescriptor*, EASY_UNIQUE_DESC(__LINE__), addBlockDescriptor(\
|
|
::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__)));\
|
|
storeBlockForce2(EASY_UNIQUE_DESC(__LINE__), EASY_RUNTIME_NAME(name), timestamp)
|
|
|
|
# define EASY_FORCE_EVENT3(ts, timestamp, name, ...)\
|
|
EASY_LOCAL_STATIC_PTR(const ::profiler::BaseBlockDescriptor*, EASY_UNIQUE_DESC(__LINE__), addBlockDescriptor(\
|
|
::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__)));\
|
|
storeBlockForce2(ts, EASY_UNIQUE_DESC(__LINE__), EASY_RUNTIME_NAME(name), timestamp)
|
|
#else
|
|
# ifndef EASY_PROFILER_API_DISABLED
|
|
# define EASY_PROFILER_API_DISABLED
|
|
# endif
|
|
# define EASY_EVENT_RES(res, name, ...)
|
|
# define EASY_FORCE_EVENT(timestamp, name, ...)
|
|
# define EASY_FORCE_EVENT2(timestamp, name, ...)
|
|
# define EASY_FORCE_EVENT3(ts, timestamp, name, ...)
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
extern "C" {
|
|
|
|
#if !defined(EASY_PROFILER_API_DISABLED)
|
|
PROFILER_API timestamp_t currentTime()
|
|
{
|
|
return getCurrentTime();
|
|
}
|
|
|
|
PROFILER_API timestamp_t toNanoseconds(timestamp_t _ticks)
|
|
{
|
|
#ifdef _WIN32
|
|
return _ticks * 1000000000LL / CPU_FREQUENCY;
|
|
#elif defined(USE_STD_CHRONO)
|
|
return _ticks;
|
|
#else
|
|
return _ticks / CPU_FREQUENCY.load(std::memory_order_acquire)
|
|
#endif
|
|
}
|
|
|
|
PROFILER_API timestamp_t toMicroseconds(timestamp_t _ticks)
|
|
{
|
|
#ifdef _WIN32
|
|
return _ticks * 1000000LL / CPU_FREQUENCY;
|
|
#elif defined(USE_STD_CHRONO)
|
|
return _ticks / 1000;
|
|
#else
|
|
return _ticks * 1000 / CPU_FREQUENCY.load(std::memory_order_acquire)
|
|
#endif
|
|
}
|
|
|
|
PROFILER_API const BaseBlockDescriptor* registerDescription(EasyBlockStatus _status, const char* _autogenUniqueId, const char* _name, const char* _filename, int _line, block_type_t _block_type, color_t _color, bool _copyName)
|
|
{
|
|
return MANAGER.addBlockDescriptor(_status, _autogenUniqueId, _name, _filename, _line, _block_type, _color, _copyName);
|
|
}
|
|
|
|
PROFILER_API void endBlock()
|
|
{
|
|
MANAGER.endBlock();
|
|
}
|
|
|
|
PROFILER_API void setEnabled(bool isEnable)
|
|
{
|
|
MANAGER.setEnabled(isEnable);
|
|
}
|
|
|
|
PROFILER_API bool isEnabled()
|
|
{
|
|
return MANAGER.isEnabled();
|
|
}
|
|
|
|
PROFILER_API void storeEvent(const BaseBlockDescriptor* _desc, const char* _runtimeName)
|
|
{
|
|
MANAGER.storeBlock(_desc, _runtimeName);
|
|
}
|
|
|
|
PROFILER_API void storeBlock(const BaseBlockDescriptor* _desc, const char* _runtimeName, timestamp_t _beginTime, timestamp_t _endTime)
|
|
{
|
|
MANAGER.storeBlock(_desc, _runtimeName, _beginTime, _endTime);
|
|
}
|
|
|
|
PROFILER_API void beginBlock(Block& _block)
|
|
{
|
|
MANAGER.beginBlock(_block);
|
|
}
|
|
|
|
PROFILER_API void beginNonScopedBlock(const BaseBlockDescriptor* _desc, const char* _runtimeName)
|
|
{
|
|
MANAGER.beginNonScopedBlock(_desc, _runtimeName);
|
|
}
|
|
|
|
PROFILER_API uint32_t dumpBlocksToFile(const char* filename)
|
|
{
|
|
return MANAGER.dumpBlocksToFile(filename);
|
|
}
|
|
|
|
PROFILER_API const char* registerThreadScoped(const char* name, ThreadGuard& threadGuard)
|
|
{
|
|
return MANAGER.registerThread(name, threadGuard);
|
|
}
|
|
|
|
PROFILER_API const char* registerThread(const char* name)
|
|
{
|
|
return MANAGER.registerThread(name);
|
|
}
|
|
|
|
PROFILER_API void setEventTracingEnabled(bool _isEnable)
|
|
{
|
|
MANAGER.setEventTracingEnabled(_isEnable);
|
|
}
|
|
|
|
PROFILER_API bool isEventTracingEnabled()
|
|
{
|
|
return MANAGER.isEventTracingEnabled();
|
|
}
|
|
|
|
# ifdef _WIN32
|
|
PROFILER_API void setLowPriorityEventTracing(bool _isLowPriority)
|
|
{
|
|
EasyEventTracer::instance().setLowPriority(_isLowPriority);
|
|
}
|
|
|
|
PROFILER_API bool isLowPriorityEventTracing()
|
|
{
|
|
return EasyEventTracer::instance().isLowPriority();
|
|
}
|
|
# else
|
|
PROFILER_API void setLowPriorityEventTracing(bool) { }
|
|
PROFILER_API bool isLowPriorityEventTracing() { return false; }
|
|
# endif
|
|
|
|
PROFILER_API void setContextSwitchLogFilename(const char* name)
|
|
{
|
|
return MANAGER.setContextSwitchLogFilename(name);
|
|
}
|
|
|
|
PROFILER_API const char* getContextSwitchLogFilename()
|
|
{
|
|
return MANAGER.getContextSwitchLogFilename();
|
|
}
|
|
|
|
PROFILER_API void startListen(uint16_t _port)
|
|
{
|
|
return MANAGER.startListen(_port);
|
|
}
|
|
|
|
PROFILER_API void stopListen()
|
|
{
|
|
return MANAGER.stopListen();
|
|
}
|
|
|
|
PROFILER_API bool isListening()
|
|
{
|
|
return MANAGER.isListening();
|
|
}
|
|
|
|
PROFILER_API bool isMainThread()
|
|
{
|
|
return THIS_THREAD_IS_MAIN;
|
|
}
|
|
|
|
PROFILER_API timestamp_t this_thread_frameTime(Duration _durationCast)
|
|
{
|
|
if (_durationCast == profiler::TICKS)
|
|
return THIS_THREAD_FRAME_T_CUR;
|
|
return TICKS_TO_US(THIS_THREAD_FRAME_T_CUR);
|
|
}
|
|
|
|
PROFILER_API timestamp_t this_thread_frameTimeLocalMax(Duration _durationCast)
|
|
{
|
|
THIS_THREAD_FRAME_T_RESET_MAX = true;
|
|
if (_durationCast == profiler::TICKS)
|
|
return THIS_THREAD_FRAME_T_MAX;
|
|
return TICKS_TO_US(THIS_THREAD_FRAME_T_MAX);
|
|
}
|
|
|
|
PROFILER_API timestamp_t this_thread_frameTimeLocalAvg(Duration _durationCast)
|
|
{
|
|
THIS_THREAD_FRAME_T_RESET_AVG = true;
|
|
auto avgDuration = THIS_THREAD_N_FRAMES > 0 ? THIS_THREAD_FRAME_T_ACC / THIS_THREAD_N_FRAMES : 0;
|
|
if (_durationCast == profiler::TICKS)
|
|
return avgDuration;
|
|
return TICKS_TO_US(avgDuration);
|
|
}
|
|
|
|
PROFILER_API timestamp_t main_thread_frameTime(Duration _durationCast)
|
|
{
|
|
const auto ticks = THIS_THREAD_IS_MAIN ? THIS_THREAD_FRAME_T_CUR : MANAGER.curFrameDuration();
|
|
if (_durationCast == profiler::TICKS)
|
|
return ticks;
|
|
return TICKS_TO_US(ticks);
|
|
}
|
|
|
|
PROFILER_API timestamp_t main_thread_frameTimeLocalMax(Duration _durationCast)
|
|
{
|
|
if (THIS_THREAD_IS_MAIN)
|
|
{
|
|
THIS_THREAD_FRAME_T_RESET_MAX = true;
|
|
if (_durationCast == profiler::TICKS)
|
|
return THIS_THREAD_FRAME_T_MAX;
|
|
return TICKS_TO_US(THIS_THREAD_FRAME_T_MAX);
|
|
}
|
|
|
|
if (_durationCast == profiler::TICKS)
|
|
return MANAGER.maxFrameDuration();
|
|
return TICKS_TO_US(MANAGER.maxFrameDuration());
|
|
}
|
|
|
|
PROFILER_API timestamp_t main_thread_frameTimeLocalAvg(Duration _durationCast)
|
|
{
|
|
if (THIS_THREAD_IS_MAIN)
|
|
{
|
|
THIS_THREAD_FRAME_T_RESET_AVG = true;
|
|
auto avgDuration = THIS_THREAD_N_FRAMES > 0 ? THIS_THREAD_FRAME_T_ACC / THIS_THREAD_N_FRAMES : 0;
|
|
if (_durationCast == profiler::TICKS)
|
|
return avgDuration;
|
|
return TICKS_TO_US(avgDuration);
|
|
}
|
|
|
|
if (_durationCast == profiler::TICKS)
|
|
return MANAGER.avgFrameDuration();
|
|
return TICKS_TO_US(MANAGER.avgFrameDuration());
|
|
}
|
|
|
|
#else
|
|
PROFILER_API timestamp_t currentTime() { return 0; }
|
|
PROFILER_API timestamp_t toNanoseconds(timestamp_t) { return 0; }
|
|
PROFILER_API timestamp_t toMicroseconds(timestamp_t) { return 0; }
|
|
PROFILER_API const BaseBlockDescriptor* registerDescription(EasyBlockStatus, const char*, const char*, const char*, int, block_type_t, color_t, bool) { return reinterpret_cast<const BaseBlockDescriptor*>(0xbad); }
|
|
PROFILER_API void endBlock() { }
|
|
PROFILER_API void setEnabled(bool) { }
|
|
PROFILER_API bool isEnabled() { return false; }
|
|
PROFILER_API void storeEvent(const BaseBlockDescriptor*, const char*) { }
|
|
PROFILER_API void storeBlock(const BaseBlockDescriptor*, const char*, timestamp_t, timestamp_t) { }
|
|
PROFILER_API void beginBlock(Block&) { }
|
|
PROFILER_API void beginNonScopedBlock(const BaseBlockDescriptor*, const char*) { }
|
|
PROFILER_API uint32_t dumpBlocksToFile(const char*) { return 0; }
|
|
PROFILER_API const char* registerThreadScoped(const char*, ThreadGuard&) { return ""; }
|
|
PROFILER_API const char* registerThread(const char*) { return ""; }
|
|
PROFILER_API void setEventTracingEnabled(bool) { }
|
|
PROFILER_API bool isEventTracingEnabled() { return false; }
|
|
PROFILER_API void setLowPriorityEventTracing(bool) { }
|
|
PROFILER_API bool isLowPriorityEventTracing(bool) { return false; }
|
|
PROFILER_API void setContextSwitchLogFilename(const char*) { }
|
|
PROFILER_API const char* getContextSwitchLogFilename() { return ""; }
|
|
PROFILER_API void startListen(uint16_t) { }
|
|
PROFILER_API void stopListen() { }
|
|
PROFILER_API bool isListening() { return false; }
|
|
|
|
PROFILER_API bool isMainThread() { return false; }
|
|
PROFILER_API timestamp_t this_thread_frameTime(Duration) { return 0; }
|
|
PROFILER_API timestamp_t this_thread_frameTimeLocalMax(Duration) { return 0; }
|
|
PROFILER_API timestamp_t this_thread_frameTimeLocalAvg(Duration) { return 0; }
|
|
PROFILER_API timestamp_t main_thread_frameTime(Duration) { return 0; }
|
|
PROFILER_API timestamp_t main_thread_frameTimeLocalMax(Duration) { return 0; }
|
|
PROFILER_API timestamp_t main_thread_frameTimeLocalAvg(Duration) { return 0; }
|
|
#endif
|
|
|
|
PROFILER_API uint8_t versionMajor()
|
|
{
|
|
static_assert(0 <= EASY_PROFILER_VERSION_MAJOR && EASY_PROFILER_VERSION_MAJOR <= 255, "EASY_PROFILER_VERSION_MAJOR must be defined in range [0, 255]");
|
|
return EASY_PROFILER_VERSION_MAJOR;
|
|
}
|
|
|
|
PROFILER_API uint8_t versionMinor()
|
|
{
|
|
static_assert(0 <= EASY_PROFILER_VERSION_MINOR && EASY_PROFILER_VERSION_MINOR <= 255, "EASY_PROFILER_VERSION_MINOR must be defined in range [0, 255]");
|
|
return EASY_PROFILER_VERSION_MINOR;
|
|
}
|
|
|
|
PROFILER_API uint16_t versionPatch()
|
|
{
|
|
static_assert(0 <= EASY_PROFILER_VERSION_PATCH && EASY_PROFILER_VERSION_PATCH <= 65535, "EASY_PROFILER_VERSION_PATCH must be defined in range [0, 65535]");
|
|
return EASY_PROFILER_VERSION_PATCH;
|
|
}
|
|
|
|
PROFILER_API uint32_t version()
|
|
{
|
|
return EASY_CURRENT_VERSION;
|
|
}
|
|
|
|
PROFILER_API const char* versionName()
|
|
{
|
|
return EASY_PROFILER_PRODUCT_VERSION
|
|
#ifdef EASY_PROFILER_API_DISABLED
|
|
"_disabled"
|
|
#endif
|
|
;
|
|
}
|
|
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
SerializedBlock::SerializedBlock(const Block& block, uint16_t name_length)
|
|
: BaseBlockData(block)
|
|
{
|
|
auto pName = const_cast<char*>(name());
|
|
if (name_length) strncpy(pName, block.name(), name_length);
|
|
pName[name_length] = 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
BaseBlockDescriptor::BaseBlockDescriptor(block_id_t _id, EasyBlockStatus _status, int _line, block_type_t _block_type, color_t _color)
|
|
: m_id(_id)
|
|
, m_line(_line)
|
|
, m_type(_block_type)
|
|
, m_color(_color)
|
|
, m_status(_status)
|
|
{
|
|
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef EASY_BLOCK_DESC_FULL_COPY
|
|
# define EASY_BLOCK_DESC_FULL_COPY 1
|
|
#endif
|
|
|
|
#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
|
|
{
|
|
friend ProfileManager;
|
|
|
|
EASY_BLOCK_DESC_STRING m_filename; ///< Source file name where this block is declared
|
|
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
|
|
|
|
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_filename(_filename)
|
|
, m_name(_name)
|
|
{
|
|
}
|
|
|
|
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.
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
NonscopedBlock::NonscopedBlock(const profiler::BaseBlockDescriptor* _desc, const char* _runtimeName, bool)
|
|
: profiler::Block(_desc, _runtimeName, false)
|
|
{
|
|
|
|
}
|
|
|
|
NonscopedBlock::~NonscopedBlock()
|
|
{
|
|
m_end = m_begin; // to restrict profiler::Block to invoke profiler::endBlock() on destructor.
|
|
}
|
|
|
|
void NonscopedBlock::copyname()
|
|
{
|
|
if ((m_status & profiler::ON) != 0 && m_name[0] != 0)
|
|
{
|
|
m_runtimeName = m_name;
|
|
m_name = m_runtimeName.c_str();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
ThreadStorage::ThreadStorage() : nonscopedBlocks(16), id(getCurrentThreadId()), allowChildren(true), named(false), guarded(false)
|
|
#ifndef _WIN32
|
|
, pthread_id(pthread_self())
|
|
#endif
|
|
|
|
{
|
|
expired = ATOMIC_VAR_INIT(0);
|
|
frame = ATOMIC_VAR_INIT(false);
|
|
}
|
|
|
|
void ThreadStorage::storeBlock(const profiler::Block& block)
|
|
{
|
|
#if EASY_OPTION_MEASURE_STORAGE_EXPAND != 0
|
|
EASY_LOCAL_STATIC_PTR(const BaseBlockDescriptor*, desc,\
|
|
MANAGER.addBlockDescriptor(EASY_OPTION_STORAGE_EXPAND_BLOCKS_ON ? profiler::ON : profiler::OFF, EASY_UNIQUE_LINE_ID, "EasyProfiler.ExpandStorage",\
|
|
__FILE__, __LINE__, profiler::BLOCK_TYPE_BLOCK, EASY_COLOR_INTERNAL_EVENT));
|
|
|
|
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()));
|
|
auto size = static_cast<uint16_t>(sizeof(BaseBlockData) + name_length + 1);
|
|
|
|
#if EASY_OPTION_MEASURE_STORAGE_EXPAND != 0
|
|
const bool expanded = (desc->m_status & profiler::ON) && blocks.closedList.need_expand(size);
|
|
if (expanded) beginTime = getCurrentTime();
|
|
#endif
|
|
|
|
auto data = blocks.closedList.allocate(size);
|
|
|
|
#if EASY_OPTION_MEASURE_STORAGE_EXPAND != 0
|
|
if (expanded) endTime = getCurrentTime();
|
|
#endif
|
|
|
|
::new (data) SerializedBlock(block, name_length);
|
|
blocks.usedMemorySize += size;
|
|
|
|
#if EASY_OPTION_MEASURE_STORAGE_EXPAND != 0
|
|
if (expanded)
|
|
{
|
|
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, 0);
|
|
blocks.usedMemorySize += size;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ThreadStorage::storeCSwitch(const profiler::Block& block)
|
|
{
|
|
auto name_length = static_cast<uint16_t>(strlen(block.name()));
|
|
auto size = static_cast<uint16_t>(sizeof(BaseBlockData) + name_length + 1);
|
|
auto data = sync.closedList.allocate(size);
|
|
::new (data) SerializedBlock(block, name_length);
|
|
sync.usedMemorySize += size;
|
|
}
|
|
|
|
void ThreadStorage::clearClosed()
|
|
{
|
|
blocks.clearClosed();
|
|
sync.clearClosed();
|
|
}
|
|
|
|
void ThreadStorage::popSilent()
|
|
{
|
|
if (!blocks.openedList.empty())
|
|
{
|
|
Block& top = blocks.openedList.back();
|
|
top.m_end = top.m_begin;
|
|
if (!top.m_isScoped)
|
|
nonscopedBlocks.pop();
|
|
blocks.openedList.pop_back();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
ThreadGuard::~ThreadGuard()
|
|
{
|
|
#ifndef EASY_PROFILER_API_DISABLED
|
|
if (m_id != 0 && THIS_THREAD != nullptr && THIS_THREAD->id == m_id)
|
|
{
|
|
bool isMarked = false;
|
|
EASY_EVENT_RES(isMarked, "ThreadFinished", EASY_COLOR_THREAD_END, ::profiler::FORCE_ON);
|
|
THIS_THREAD->frame.store(false, std::memory_order_release);
|
|
THIS_THREAD->expired.store(isMarked ? 2 : 1, std::memory_order_release);
|
|
THIS_THREAD = nullptr;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
ProfileManager::ProfileManager() :
|
|
#ifdef _WIN32
|
|
m_processId(GetProcessId(GetCurrentProcess()))
|
|
#else
|
|
m_processId((processid_t)getpid())
|
|
#endif
|
|
, m_usedMemorySize(0)
|
|
, m_beginTime(0)
|
|
, m_endTime(0)
|
|
{
|
|
m_profilerStatus = ATOMIC_VAR_INIT(EASY_PROF_DISABLED);
|
|
m_isEventTracingEnabled = ATOMIC_VAR_INIT(EASY_OPTION_EVENT_TRACING_ENABLED);
|
|
m_isAlreadyListening = ATOMIC_VAR_INIT(false);
|
|
m_stopListen = ATOMIC_VAR_INIT(false);
|
|
|
|
m_mainThreadId = ATOMIC_VAR_INIT(0);
|
|
m_frameMax = ATOMIC_VAR_INIT(0);
|
|
m_frameAvg = ATOMIC_VAR_INIT(0);
|
|
m_frameCur = ATOMIC_VAR_INIT(0);
|
|
m_frameMaxReset = ATOMIC_VAR_INIT(false);
|
|
m_frameAvgReset = ATOMIC_VAR_INIT(false);
|
|
|
|
#if !defined(EASY_PROFILER_API_DISABLED) && EASY_OPTION_START_LISTEN_ON_STARTUP != 0
|
|
startListen(profiler::DEFAULT_PORT);
|
|
#endif
|
|
|
|
#if !defined(EASY_PROFILER_API_DISABLED) && !defined(_WIN32) && !defined(USE_STD_CHRONO)
|
|
const int64_t cpu_frequency = calculate_cpu_frequency();
|
|
CPU_FREQUENCY.store(cpu_frequency, std::memory_order_release);
|
|
#endif
|
|
}
|
|
|
|
ProfileManager::~ProfileManager()
|
|
{
|
|
#ifndef EASY_PROFILER_API_DISABLED
|
|
stopListen();
|
|
#endif
|
|
|
|
for (auto desc : m_descriptors) {
|
|
#if EASY_BLOCK_DESC_FULL_COPY == 0
|
|
if (desc)
|
|
desc->~BlockDescriptor();
|
|
free(desc);
|
|
#else
|
|
delete desc;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#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 profileManager;
|
|
return profileManager;
|
|
#endif
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
ThreadStorage& ProfileManager::_threadStorage(profiler::thread_id_t _thread_id)
|
|
{
|
|
return m_threads[_thread_id];
|
|
}
|
|
|
|
ThreadStorage* ProfileManager::_findThreadStorage(profiler::thread_id_t _thread_id)
|
|
{
|
|
auto it = m_threads.find(_thread_id);
|
|
return it != m_threads.end() ? &it->second : nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
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,
|
|
bool _copyName)
|
|
{
|
|
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];
|
|
|
|
const auto nameLen = strlen(_name);
|
|
m_usedMemorySize += sizeof(profiler::SerializedBlockDescriptor) + nameLen + strlen(_filename) + 2;
|
|
|
|
#if EASY_BLOCK_DESC_FULL_COPY == 0
|
|
BlockDescriptor* desc = nullptr;
|
|
|
|
if (_copyName)
|
|
{
|
|
void* data = malloc(sizeof(BlockDescriptor) + nameLen + 1);
|
|
char* name = reinterpret_cast<char*>(data) + sizeof(BlockDescriptor);
|
|
strncpy(name, _name, nameLen);
|
|
desc = ::new (data)BlockDescriptor(static_cast<block_id_t>(m_descriptors.size()), _defaultStatus, name, _filename, _line, _block_type, _color);
|
|
}
|
|
else
|
|
{
|
|
void* data = malloc(sizeof(BlockDescriptor));
|
|
desc = ::new (data)BlockDescriptor(static_cast<block_id_t>(m_descriptors.size()), _defaultStatus, _name, _filename, _line, _block_type, _color);
|
|
}
|
|
#else
|
|
auto desc = new BlockDescriptor(static_cast<block_id_t>(m_descriptors.size()), _defaultStatus, _name, _filename, _line, _block_type, _color);
|
|
#endif
|
|
|
|
m_descriptors.emplace_back(desc);
|
|
m_descriptorsMap.emplace(key, desc->id());
|
|
|
|
return desc;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
bool ProfileManager::storeBlock(const profiler::BaseBlockDescriptor* _desc, const char* _runtimeName)
|
|
{
|
|
const auto state = m_profilerStatus.load(std::memory_order_acquire);
|
|
if (state == EASY_PROF_DISABLED || !(_desc->m_status & profiler::ON))
|
|
return false;
|
|
|
|
if (state == EASY_PROF_DUMP)
|
|
{
|
|
if (THIS_THREAD == nullptr || THIS_THREAD->blocks.openedList.empty())
|
|
return false;
|
|
}
|
|
else if (THIS_THREAD == nullptr)
|
|
{
|
|
THIS_THREAD = &threadStorage(getCurrentThreadId());
|
|
}
|
|
|
|
#if EASY_ENABLE_BLOCK_STATUS != 0
|
|
if (!THIS_THREAD->allowChildren && !(_desc->m_status & FORCE_ON_FLAG))
|
|
return false;
|
|
#endif
|
|
|
|
profiler::Block b(_desc, _runtimeName);
|
|
b.start();
|
|
b.m_end = b.m_begin;
|
|
|
|
THIS_THREAD->storeBlock(b);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ProfileManager::storeBlock(const profiler::BaseBlockDescriptor* _desc, const char* _runtimeName, profiler::timestamp_t _beginTime, profiler::timestamp_t _endTime)
|
|
{
|
|
const auto state = m_profilerStatus.load(std::memory_order_acquire);
|
|
if (state == EASY_PROF_DISABLED || !(_desc->m_status & profiler::ON))
|
|
return false;
|
|
|
|
if (state == EASY_PROF_DUMP)
|
|
{
|
|
if (THIS_THREAD == nullptr || THIS_THREAD->blocks.openedList.empty())
|
|
return false;
|
|
}
|
|
else if (THIS_THREAD == nullptr)
|
|
{
|
|
THIS_THREAD = &threadStorage(getCurrentThreadId());
|
|
}
|
|
|
|
#if EASY_ENABLE_BLOCK_STATUS != 0
|
|
if (!THIS_THREAD->allowChildren && !(_desc->m_status & FORCE_ON_FLAG))
|
|
return false;
|
|
#endif
|
|
|
|
profiler::Block b(_beginTime, _endTime, _desc->id(), _runtimeName);
|
|
THIS_THREAD->storeBlock(b);
|
|
b.m_end = b.m_begin;
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void ProfileManager::storeBlockForce(const profiler::BaseBlockDescriptor* _desc, const char* _runtimeName, ::profiler::timestamp_t& _timestamp)
|
|
{
|
|
if (!(_desc->m_status & profiler::ON))
|
|
return;
|
|
|
|
if (THIS_THREAD == nullptr)
|
|
THIS_THREAD = &threadStorage(getCurrentThreadId());
|
|
|
|
#if EASY_ENABLE_BLOCK_STATUS != 0
|
|
if (!THIS_THREAD->allowChildren && !(_desc->m_status & FORCE_ON_FLAG))
|
|
return;
|
|
#endif
|
|
|
|
profiler::Block b(_desc, _runtimeName);
|
|
b.start();
|
|
b.m_end = b.m_begin;
|
|
|
|
_timestamp = b.m_begin;
|
|
THIS_THREAD->storeBlock(b);
|
|
}
|
|
|
|
void ProfileManager::storeBlockForce2(const profiler::BaseBlockDescriptor* _desc, const char* _runtimeName, ::profiler::timestamp_t _timestamp)
|
|
{
|
|
if (!(_desc->m_status & profiler::ON))
|
|
return;
|
|
|
|
if (THIS_THREAD == nullptr)
|
|
THIS_THREAD = &threadStorage(getCurrentThreadId());
|
|
|
|
#if EASY_ENABLE_BLOCK_STATUS != 0
|
|
if (!THIS_THREAD->allowChildren && !(_desc->m_status & FORCE_ON_FLAG))
|
|
return;
|
|
#endif
|
|
|
|
profiler::Block b(_desc, _runtimeName);
|
|
b.m_end = b.m_begin = _timestamp;
|
|
|
|
THIS_THREAD->storeBlock(b);
|
|
}
|
|
|
|
void ProfileManager::storeBlockForce2(ThreadStorage& _registeredThread, const profiler::BaseBlockDescriptor* _desc, const char* _runtimeName, ::profiler::timestamp_t _timestamp)
|
|
{
|
|
profiler::Block b(_desc, _runtimeName);
|
|
b.m_end = b.m_begin = _timestamp;
|
|
_registeredThread.storeBlock(b);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void ProfileManager::beginBlock(Block& _block)
|
|
{
|
|
if (THIS_THREAD == nullptr)
|
|
THIS_THREAD = &threadStorage(getCurrentThreadId());
|
|
|
|
if (++THIS_THREAD_STACK_SIZE > 1)
|
|
{
|
|
_block.m_status = profiler::OFF;
|
|
THIS_THREAD->blocks.openedList.emplace_back(_block);
|
|
return;
|
|
}
|
|
|
|
const auto state = m_profilerStatus.load(std::memory_order_acquire);
|
|
if (state == EASY_PROF_DISABLED)
|
|
{
|
|
THIS_THREAD_HALT = false;
|
|
_block.m_status = profiler::OFF;
|
|
THIS_THREAD->blocks.openedList.emplace_back(_block);
|
|
beginFrame();
|
|
return;
|
|
}
|
|
|
|
bool empty = true;
|
|
if (state == EASY_PROF_DUMP)
|
|
{
|
|
if (THIS_THREAD_HALT || THIS_THREAD->blocks.openedList.empty())
|
|
{
|
|
_block.m_status = profiler::OFF;
|
|
THIS_THREAD->blocks.openedList.emplace_back(_block);
|
|
|
|
if (!THIS_THREAD_HALT)
|
|
{
|
|
THIS_THREAD_HALT = true;
|
|
beginFrame();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
empty = false;
|
|
}
|
|
else
|
|
{
|
|
empty = THIS_THREAD->blocks.openedList.empty();
|
|
}
|
|
|
|
THIS_THREAD_HALT = false;
|
|
THIS_THREAD_STACK_SIZE = 0;
|
|
|
|
#if EASY_ENABLE_BLOCK_STATUS != 0
|
|
if (THIS_THREAD->allowChildren)
|
|
{
|
|
#endif
|
|
if (_block.m_status & profiler::ON)
|
|
_block.start();
|
|
#if EASY_ENABLE_BLOCK_STATUS != 0
|
|
THIS_THREAD->allowChildren = !(_block.m_status & profiler::OFF_RECURSIVE);
|
|
}
|
|
else if (_block.m_status & FORCE_ON_FLAG)
|
|
{
|
|
_block.start();
|
|
_block.m_status = profiler::FORCE_ON_WITHOUT_CHILDREN;
|
|
}
|
|
else
|
|
{
|
|
_block.m_status = profiler::OFF_RECURSIVE;
|
|
}
|
|
#endif
|
|
|
|
if (empty)
|
|
{
|
|
beginFrame();
|
|
THIS_THREAD->frame.store(true, std::memory_order_release);
|
|
}
|
|
|
|
THIS_THREAD->blocks.openedList.emplace_back(_block);
|
|
}
|
|
|
|
void ProfileManager::beginNonScopedBlock(const profiler::BaseBlockDescriptor* _desc, const char* _runtimeName)
|
|
{
|
|
if (THIS_THREAD == nullptr)
|
|
THIS_THREAD = &threadStorage(getCurrentThreadId());
|
|
|
|
NonscopedBlock& b = THIS_THREAD->nonscopedBlocks.push(_desc, _runtimeName, false);
|
|
beginBlock(b);
|
|
b.copyname();
|
|
}
|
|
|
|
void ProfileManager::beginContextSwitch(profiler::thread_id_t _thread_id, profiler::timestamp_t _time, profiler::thread_id_t _target_thread_id, const char* _target_process, bool _lockSpin)
|
|
{
|
|
auto ts = _lockSpin ? findThreadStorage(_thread_id) : _findThreadStorage(_thread_id);
|
|
if (ts != nullptr)
|
|
// Dirty hack: _target_thread_id will be written to the field "block_id_t m_id"
|
|
// and will be available calling method id().
|
|
ts->sync.openedList.emplace_back(_time, _time, _target_thread_id, _target_process);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void ProfileManager::endBlock()
|
|
{
|
|
if (--THIS_THREAD_STACK_SIZE > 0)
|
|
{
|
|
THIS_THREAD->popSilent();
|
|
return;
|
|
}
|
|
|
|
THIS_THREAD_STACK_SIZE = 0;
|
|
if (THIS_THREAD_HALT || m_profilerStatus.load(std::memory_order_acquire) == EASY_PROF_DISABLED)
|
|
{
|
|
THIS_THREAD->popSilent();
|
|
endFrame();
|
|
return;
|
|
}
|
|
|
|
if (THIS_THREAD->blocks.openedList.empty())
|
|
return;
|
|
|
|
Block& top = THIS_THREAD->blocks.openedList.back();
|
|
if (top.m_status & profiler::ON)
|
|
{
|
|
if (!top.finished())
|
|
top.finish();
|
|
THIS_THREAD->storeBlock(top);
|
|
}
|
|
else
|
|
{
|
|
top.m_end = top.m_begin; // this is to restrict endBlock() call inside ~Block()
|
|
}
|
|
|
|
if (!top.m_isScoped)
|
|
THIS_THREAD->nonscopedBlocks.pop();
|
|
|
|
THIS_THREAD->blocks.openedList.pop_back();
|
|
const bool empty = THIS_THREAD->blocks.openedList.empty();
|
|
if (empty)
|
|
{
|
|
THIS_THREAD->frame.store(false, std::memory_order_release);
|
|
endFrame();
|
|
#if EASY_ENABLE_BLOCK_STATUS != 0
|
|
THIS_THREAD->allowChildren = true;
|
|
}
|
|
else
|
|
{
|
|
THIS_THREAD->allowChildren = !(THIS_THREAD->blocks.openedList.back().get().m_status & profiler::OFF_RECURSIVE);
|
|
}
|
|
#else
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ProfileManager::endContextSwitch(profiler::thread_id_t _thread_id, processid_t _process_id, profiler::timestamp_t _endtime, bool _lockSpin)
|
|
{
|
|
ThreadStorage* ts = nullptr;
|
|
if (_process_id == m_processId)
|
|
// If thread owned by current process then create new ThreadStorage if there is no one
|
|
ts = _lockSpin ? &threadStorage(_thread_id) : &_threadStorage(_thread_id);
|
|
else
|
|
// If thread owned by another process OR _process_id IS UNKNOWN then do not create ThreadStorage for this
|
|
ts = _lockSpin ? findThreadStorage(_thread_id) : _findThreadStorage(_thread_id);
|
|
|
|
if (ts == nullptr || ts->sync.openedList.empty())
|
|
return;
|
|
|
|
Block& lastBlock = ts->sync.openedList.back();
|
|
lastBlock.m_end = _endtime;
|
|
|
|
ts->storeCSwitch(lastBlock);
|
|
ts->sync.openedList.pop_back();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void ProfileManager::beginFrame()
|
|
{
|
|
if (!THIS_THREAD_FRAME)
|
|
{
|
|
THIS_THREAD_FRAME_T = getCurrentTime();
|
|
THIS_THREAD_FRAME = true;
|
|
}
|
|
}
|
|
|
|
void ProfileManager::endFrame()
|
|
{
|
|
if (!THIS_THREAD_FRAME)
|
|
return;
|
|
|
|
const profiler::timestamp_t duration = getCurrentTime() - THIS_THREAD_FRAME_T;
|
|
|
|
THIS_THREAD_FRAME = false;
|
|
|
|
if (THIS_THREAD_FRAME_T_RESET_MAX)
|
|
{
|
|
THIS_THREAD_FRAME_T_RESET_MAX = false;
|
|
THIS_THREAD_FRAME_T_MAX = 0;
|
|
}
|
|
|
|
THIS_THREAD_FRAME_T_CUR = duration;
|
|
if (duration > THIS_THREAD_FRAME_T_MAX)
|
|
THIS_THREAD_FRAME_T_MAX = duration;
|
|
|
|
if (THIS_THREAD_N_FRAMES > 10000)
|
|
THIS_THREAD_FRAME_T_RESET_AVG = true;
|
|
|
|
if (THIS_THREAD_IS_MAIN)
|
|
{
|
|
if (m_frameAvgReset.exchange(false, std::memory_order_release) || THIS_THREAD_FRAME_T_RESET_AVG)
|
|
{
|
|
if (THIS_THREAD_N_FRAMES > 0)
|
|
m_frameAvg.store(THIS_THREAD_FRAME_T_ACC / THIS_THREAD_N_FRAMES, std::memory_order_release);
|
|
THIS_THREAD_FRAME_T_RESET_AVG = false;
|
|
THIS_THREAD_FRAME_T_ACC = duration;
|
|
THIS_THREAD_N_FRAMES = 1;
|
|
}
|
|
else
|
|
{
|
|
THIS_THREAD_FRAME_T_ACC += duration;
|
|
++THIS_THREAD_N_FRAMES;
|
|
m_frameAvg.store(THIS_THREAD_FRAME_T_ACC / THIS_THREAD_N_FRAMES, std::memory_order_release);
|
|
}
|
|
|
|
auto maxDuration = m_frameMax.load(std::memory_order_acquire);
|
|
if (m_frameMaxReset.exchange(false, std::memory_order_release))
|
|
maxDuration = 0;
|
|
|
|
if (duration > maxDuration)
|
|
m_frameMax.store(duration, std::memory_order_release);
|
|
|
|
if (m_frameMaxReset.exchange(false, std::memory_order_release))
|
|
maxDuration = 0;
|
|
|
|
m_frameCur.store(duration, std::memory_order_release);
|
|
}
|
|
else if (THIS_THREAD_FRAME_T_RESET_AVG)
|
|
{
|
|
THIS_THREAD_FRAME_T_RESET_AVG = false;
|
|
THIS_THREAD_FRAME_T_ACC = duration;
|
|
THIS_THREAD_N_FRAMES = 1;
|
|
}
|
|
else
|
|
{
|
|
THIS_THREAD_FRAME_T_ACC += duration;
|
|
++THIS_THREAD_N_FRAMES;
|
|
}
|
|
}
|
|
|
|
profiler::timestamp_t ProfileManager::maxFrameDuration()
|
|
{
|
|
auto duration = m_frameMax.load(std::memory_order_acquire);
|
|
m_frameMaxReset.store(true, std::memory_order_release);
|
|
return duration;
|
|
}
|
|
|
|
profiler::timestamp_t ProfileManager::avgFrameDuration()
|
|
{
|
|
auto duration = m_frameAvg.load(std::memory_order_acquire);
|
|
m_frameAvgReset.store(true, std::memory_order_release);
|
|
return duration;
|
|
}
|
|
|
|
profiler::timestamp_t ProfileManager::curFrameDuration() const
|
|
{
|
|
return m_frameCur.load(std::memory_order_acquire);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void ProfileManager::enableEventTracer()
|
|
{
|
|
#ifdef _WIN32
|
|
if (m_isEventTracingEnabled.load(std::memory_order_acquire))
|
|
EasyEventTracer::instance().enable(true);
|
|
#endif
|
|
}
|
|
|
|
void ProfileManager::disableEventTracer()
|
|
{
|
|
#ifdef _WIN32
|
|
EasyEventTracer::instance().disable();
|
|
#endif
|
|
}
|
|
|
|
void ProfileManager::setEnabled(bool isEnable)
|
|
{
|
|
guard_lock_t lock(m_dumpSpin);
|
|
|
|
auto time = getCurrentTime();
|
|
const auto status = isEnable ? EASY_PROF_ENABLED : EASY_PROF_DISABLED;
|
|
const auto prev = m_profilerStatus.exchange(status, std::memory_order_release);
|
|
if (prev == status)
|
|
return;
|
|
|
|
if (isEnable)
|
|
{
|
|
EASY_LOGMSG("Enabled profiling\n");
|
|
enableEventTracer();
|
|
m_beginTime = time;
|
|
}
|
|
else
|
|
{
|
|
EASY_LOGMSG("Disabled profiling\n");
|
|
disableEventTracer();
|
|
m_endTime = time;
|
|
}
|
|
}
|
|
|
|
bool ProfileManager::isEnabled() const
|
|
{
|
|
return m_profilerStatus.load(std::memory_order_acquire) == EASY_PROF_ENABLED;
|
|
}
|
|
|
|
void ProfileManager::setEventTracingEnabled(bool _isEnable)
|
|
{
|
|
m_isEventTracingEnabled.store(_isEnable, std::memory_order_release);
|
|
}
|
|
|
|
bool ProfileManager::isEventTracingEnabled() const
|
|
{
|
|
return m_isEventTracingEnabled.load(std::memory_order_acquire);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
char ProfileManager::checkThreadExpired(ThreadStorage& _registeredThread)
|
|
{
|
|
const char val = _registeredThread.expired.load(std::memory_order_acquire);
|
|
if (val != 0)
|
|
return val;
|
|
|
|
if (_registeredThread.guarded)
|
|
return 0;
|
|
|
|
#ifdef _WIN32
|
|
|
|
// Check thread for Windows
|
|
|
|
DWORD exitCode = 0;
|
|
auto hThread = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, _registeredThread.id);
|
|
if (hThread == nullptr || GetExitCodeThread(hThread, &exitCode) == FALSE || exitCode != STILL_ACTIVE)
|
|
{
|
|
// Thread has been expired
|
|
_registeredThread.expired.store(1, std::memory_order_release);
|
|
if (hThread != nullptr)
|
|
CloseHandle(hThread);
|
|
return 1;
|
|
}
|
|
|
|
if (hThread != nullptr)
|
|
CloseHandle(hThread);
|
|
|
|
return 0;
|
|
|
|
#else
|
|
|
|
return 0;//pthread_kill(_registeredThread.pthread_id, 0) != 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
uint32_t ProfileManager::dumpBlocksToStream(profiler::OStream& _outputStream, bool _lockSpin)
|
|
{
|
|
EASY_LOGMSG("dumpBlocksToStream(_lockSpin = " << _lockSpin << ")...\n");
|
|
|
|
if (_lockSpin)
|
|
m_dumpSpin.lock();
|
|
|
|
const auto state = m_profilerStatus.load(std::memory_order_acquire);
|
|
|
|
#ifndef _WIN32
|
|
const bool eventTracingEnabled = m_isEventTracingEnabled.load(std::memory_order_acquire);
|
|
#endif
|
|
|
|
if (state == EASY_PROF_ENABLED) {
|
|
m_profilerStatus.store(EASY_PROF_DUMP, std::memory_order_release);
|
|
disableEventTracer();
|
|
m_endTime = getCurrentTime();
|
|
}
|
|
|
|
|
|
// This is to make sure that no new descriptors or new threads will be
|
|
// added until we finish sending data.
|
|
//m_spin.lock();
|
|
// This is the only place using both spins, so no dead-lock will occur
|
|
|
|
|
|
// Wait for some time to be sure that all operations which began before setEnabled(false) will be finished.
|
|
// This is much better than inserting spin-lock or atomic variable check into each store operation.
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
|
|
|
// wait for all threads finish opened frames
|
|
EASY_LOG_ONLY(bool logged = false);
|
|
for (auto it = m_threads.begin(), end = m_threads.end(); it != end;)
|
|
{
|
|
if (!it->second.frame.load(std::memory_order_acquire))
|
|
{
|
|
++it;
|
|
EASY_LOG_ONLY(logged = false);
|
|
}
|
|
else
|
|
{
|
|
EASY_LOG_ONLY(
|
|
if (!logged)
|
|
{
|
|
logged = true;
|
|
if (it->second.named)
|
|
EASY_WARNING("Waiting for thread \"" << it->second.name << "\" finish opened frame (which is top EASY_BLOCK for this thread)...\n");
|
|
else
|
|
EASY_WARNING("Waiting for thread " << it->first << " finish opened frame (which is top EASY_BLOCK for this thread)...\n");
|
|
}
|
|
);
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
}
|
|
}
|
|
|
|
m_profilerStatus.store(EASY_PROF_DISABLED, std::memory_order_release);
|
|
|
|
EASY_LOGMSG("All threads have closed frames\n");
|
|
EASY_LOGMSG("Disabled profiling\n");
|
|
|
|
m_spin.lock();
|
|
m_storedSpin.lock();
|
|
// TODO: think about better solution because this one is not 100% safe...
|
|
|
|
const profiler::timestamp_t now = getCurrentTime();
|
|
const profiler::timestamp_t endtime = m_endTime == 0 ? now : std::min(now, m_endTime);
|
|
|
|
#ifndef _WIN32
|
|
if (eventTracingEnabled)
|
|
{
|
|
// Read thread context switch events from temporary file
|
|
|
|
EASY_LOGMSG("Writing context switch events...\n");
|
|
|
|
uint64_t timestamp = 0;
|
|
uint32_t thread_from = 0, thread_to = 0;
|
|
|
|
std::ifstream infile(m_csInfoFilename.c_str());
|
|
if(infile.is_open())
|
|
{
|
|
EASY_LOG_ONLY(uint32_t num = 0);
|
|
std::string next_task_name;
|
|
pid_t process_to = 0;
|
|
while (infile >> timestamp >> thread_from >> thread_to >> next_task_name >> process_to)
|
|
{
|
|
beginContextSwitch(thread_from, timestamp, thread_to, next_task_name.c_str(), false);
|
|
endContextSwitch(thread_to, (processid_t)process_to, timestamp, false);
|
|
EASY_LOG_ONLY(++num);
|
|
}
|
|
|
|
EASY_LOGMSG("Done, " << num << " context switch events wrote\n");
|
|
}
|
|
EASY_LOG_ONLY(
|
|
else {
|
|
EASY_ERROR("Can not open context switch log-file \"" << m_csInfoFilename << "\"\n");
|
|
}
|
|
)
|
|
}
|
|
#endif
|
|
|
|
bool mainThreadExpired = false;
|
|
|
|
// Calculate used memory total size and total blocks number
|
|
uint64_t usedMemorySize = 0;
|
|
uint32_t blocks_number = 0;
|
|
for (auto it = m_threads.begin(), end = m_threads.end(); it != end;)
|
|
{
|
|
auto& t = it->second;
|
|
uint32_t num = static_cast<uint32_t>(t.blocks.closedList.size()) + static_cast<uint32_t>(t.sync.closedList.size());
|
|
|
|
const char expired = checkThreadExpired(t);
|
|
if (num == 0 && (expired != 0 || !t.guarded)) {
|
|
// Remove thread if it contains no profiled information and has been finished or is not guarded.
|
|
profiler::thread_id_t id = it->first;
|
|
if (!mainThreadExpired && m_mainThreadId.compare_exchange_weak(id, 0, std::memory_order_release, std::memory_order_acquire))
|
|
mainThreadExpired = true;
|
|
m_threads.erase(it++);
|
|
continue;
|
|
}
|
|
|
|
if (expired == 1) {
|
|
EASY_FORCE_EVENT3(t, endtime, "ThreadExpired", EASY_COLOR_THREAD_END);
|
|
++num;
|
|
}
|
|
|
|
usedMemorySize += t.blocks.usedMemorySize + t.sync.usedMemorySize;
|
|
blocks_number += num;
|
|
++it;
|
|
}
|
|
|
|
// Write profiler signature and version
|
|
_outputStream.write(PROFILER_SIGNATURE);
|
|
_outputStream.write(EASY_CURRENT_VERSION);
|
|
_outputStream.write(m_processId);
|
|
|
|
// Write CPU frequency to let GUI calculate real time value from CPU clocks
|
|
#ifdef _WIN32
|
|
_outputStream.write(CPU_FREQUENCY);
|
|
#else
|
|
|
|
#if !defined(USE_STD_CHRONO)
|
|
EASY_LOGMSG("Calculating CPU frequency\n");
|
|
const int64_t cpu_frequency = calculate_cpu_frequency();
|
|
_outputStream.write(cpu_frequency * 1000LL);
|
|
EASY_LOGMSG("Done calculating CPU frequency\n");
|
|
|
|
CPU_FREQUENCY.store(cpu_frequency, std::memory_order_release);
|
|
#else
|
|
_outputStream.write(0LL);
|
|
#endif
|
|
#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);
|
|
_outputStream.write(static_cast<uint32_t>(m_descriptors.size()));
|
|
_outputStream.write(m_usedMemorySize);
|
|
|
|
// Write block descriptors
|
|
for (const auto descriptor : m_descriptors)
|
|
{
|
|
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->filename(), filename_size);
|
|
}
|
|
|
|
// Write blocks and context switch events for each thread
|
|
for (auto it = m_threads.begin(), end = m_threads.end(); it != end;)
|
|
{
|
|
auto& t = it->second;
|
|
|
|
_outputStream.write(it->first);
|
|
|
|
const auto name_size = static_cast<uint16_t>(t.name.size() + 1);
|
|
_outputStream.write(name_size);
|
|
_outputStream.write(name_size > 1 ? t.name.c_str() : "", name_size);
|
|
|
|
_outputStream.write(t.sync.closedList.size());
|
|
if (!t.sync.closedList.empty())
|
|
t.sync.closedList.serialize(_outputStream);
|
|
|
|
_outputStream.write(t.blocks.closedList.size());
|
|
if (!t.blocks.closedList.empty())
|
|
t.blocks.closedList.serialize(_outputStream);
|
|
|
|
t.clearClosed();
|
|
//t.blocks.openedList.clear();
|
|
t.sync.openedList.clear();
|
|
|
|
if (t.expired.load(std::memory_order_acquire) != 0)
|
|
{
|
|
// Remove expired thread after writing all profiled information
|
|
profiler::thread_id_t id = it->first;
|
|
if (!mainThreadExpired && m_mainThreadId.compare_exchange_weak(id, 0, std::memory_order_release, std::memory_order_acquire))
|
|
mainThreadExpired = true;
|
|
m_threads.erase(it++);
|
|
}
|
|
else
|
|
{
|
|
++it;
|
|
}
|
|
}
|
|
|
|
m_storedSpin.unlock();
|
|
m_spin.unlock();
|
|
|
|
if (_lockSpin)
|
|
m_dumpSpin.unlock();
|
|
|
|
EASY_LOGMSG("Done dumpBlocksToStream(). Dumped " << blocks_number << " blocks\n");
|
|
|
|
return blocks_number;
|
|
}
|
|
|
|
uint32_t ProfileManager::dumpBlocksToFile(const char* _filename)
|
|
{
|
|
EASY_LOGMSG("dumpBlocksToFile(\"" << _filename << "\")...\n");
|
|
|
|
std::ofstream outputFile(_filename, std::fstream::binary);
|
|
if (!outputFile.is_open())
|
|
{
|
|
EASY_ERROR("Can not open \"" << _filename << "\" for writing\n");
|
|
return 0;
|
|
}
|
|
|
|
profiler::OStream outputStream;
|
|
|
|
// Replace outputStream buffer to outputFile buffer to avoid redundant copying
|
|
typedef ::std::basic_iostream<std::stringstream::char_type, std::stringstream::traits_type> stringstream_parent;
|
|
stringstream_parent& s = outputStream.stream();
|
|
auto oldbuf = s.rdbuf(outputFile.rdbuf());
|
|
|
|
// Write data directly to file
|
|
const auto blocksNumber = dumpBlocksToStream(outputStream, true);
|
|
|
|
// Restore old outputStream buffer to avoid possible second memory free on stringstream destructor
|
|
s.rdbuf(oldbuf);
|
|
|
|
EASY_LOGMSG("Done dumpBlocksToFile()\n");
|
|
|
|
return blocksNumber;
|
|
}
|
|
|
|
const char* ProfileManager::registerThread(const char* name, ThreadGuard& threadGuard)
|
|
{
|
|
if (THIS_THREAD == nullptr)
|
|
THIS_THREAD = &threadStorage(getCurrentThreadId());
|
|
|
|
THIS_THREAD->guarded = true;
|
|
if (!THIS_THREAD->named)
|
|
{
|
|
THIS_THREAD->named = true;
|
|
THIS_THREAD->name = name;
|
|
|
|
if (THIS_THREAD->name == "Main")
|
|
{
|
|
profiler::thread_id_t id = 0;
|
|
THIS_THREAD_IS_MAIN = m_mainThreadId.compare_exchange_weak(id, THIS_THREAD->id, std::memory_order_release, std::memory_order_acquire);
|
|
}
|
|
}
|
|
|
|
threadGuard.m_id = THIS_THREAD->id;
|
|
|
|
return THIS_THREAD->name.c_str();
|
|
}
|
|
|
|
const char* ProfileManager::registerThread(const char* name)
|
|
{
|
|
if (THIS_THREAD == nullptr)
|
|
THIS_THREAD = &threadStorage(getCurrentThreadId());
|
|
|
|
if (!THIS_THREAD->named)
|
|
{
|
|
THIS_THREAD->named = true;
|
|
THIS_THREAD->name = name;
|
|
|
|
if (THIS_THREAD->name == "Main")
|
|
{
|
|
profiler::thread_id_t id = 0;
|
|
THIS_THREAD_IS_MAIN = m_mainThreadId.compare_exchange_weak(id, THIS_THREAD->id, std::memory_order_release, std::memory_order_acquire);
|
|
}
|
|
}
|
|
|
|
return THIS_THREAD->name.c_str();
|
|
}
|
|
|
|
void ProfileManager::setBlockStatus(block_id_t _id, EasyBlockStatus _status)
|
|
{
|
|
if (m_profilerStatus.load(std::memory_order_acquire) != EASY_PROF_ENABLED)
|
|
return; // Changing blocks statuses is restricted while profile session is active
|
|
|
|
guard_lock_t lock(m_storedSpin);
|
|
if (_id < m_descriptors.size())
|
|
{
|
|
auto desc = m_descriptors[_id];
|
|
lock.unlock();
|
|
desc->m_status = _status;
|
|
}
|
|
}
|
|
|
|
void ProfileManager::startListen(uint16_t _port)
|
|
{
|
|
if (!m_isAlreadyListening.exchange(true, std::memory_order_release))
|
|
{
|
|
m_stopListen.store(false, std::memory_order_release);
|
|
m_listenThread = std::thread(&ProfileManager::listen, this, _port);
|
|
}
|
|
}
|
|
|
|
void ProfileManager::stopListen()
|
|
{
|
|
m_stopListen.store(true, std::memory_order_release);
|
|
if (m_listenThread.joinable())
|
|
m_listenThread.join();
|
|
m_isAlreadyListening.store(false, std::memory_order_release);
|
|
|
|
EASY_LOGMSG("Listening stopped\n");
|
|
}
|
|
|
|
bool ProfileManager::isListening() const
|
|
{
|
|
return m_isAlreadyListening.load(std::memory_order_acquire);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void ProfileManager::listen(uint16_t _port)
|
|
{
|
|
EASY_THREAD_SCOPE("EasyProfiler.Listen");
|
|
|
|
EASY_LOGMSG("Listening started\n");
|
|
|
|
EasySocket socket;
|
|
profiler::net::Message replyMessage(profiler::net::MESSAGE_TYPE_REPLY_START_CAPTURING);
|
|
|
|
socket.bind(_port);
|
|
int bytes = 0;
|
|
while (!m_stopListen.load(std::memory_order_acquire))
|
|
{
|
|
bool hasConnect = false;
|
|
|
|
socket.listen();
|
|
socket.accept();
|
|
|
|
EASY_EVENT("ClientConnected", EASY_COLOR_INTERNAL_EVENT, profiler::OFF);
|
|
hasConnect = true;
|
|
|
|
EASY_LOGMSG("GUI-client connected\n");
|
|
|
|
// Send reply
|
|
{
|
|
const bool wasLowPriorityET =
|
|
#ifdef _WIN32
|
|
EasyEventTracer::instance().isLowPriority();
|
|
#else
|
|
false;
|
|
#endif
|
|
const profiler::net::EasyProfilerStatus connectionReply(m_profilerStatus.load(std::memory_order_acquire) == EASY_PROF_ENABLED, m_isEventTracingEnabled.load(std::memory_order_acquire), wasLowPriorityET);
|
|
bytes = socket.send(&connectionReply, sizeof(profiler::net::EasyProfilerStatus));
|
|
hasConnect = bytes > 0;
|
|
}
|
|
|
|
while (hasConnect && !m_stopListen.load(std::memory_order_acquire))
|
|
{
|
|
char buffer[256] = {};
|
|
|
|
bytes = socket.receive(buffer, 255);
|
|
|
|
hasConnect = bytes > 0;
|
|
|
|
char *buf = &buffer[0];
|
|
|
|
if (bytes > 0)
|
|
{
|
|
profiler::net::Message* message = (profiler::net::Message*)buf;
|
|
if (!message->isEasyNetMessage()){
|
|
continue;
|
|
}
|
|
|
|
switch (message->type)
|
|
{
|
|
case profiler::net::MESSAGE_TYPE_CHECK_CONNECTION:
|
|
{
|
|
EASY_LOGMSG("receive MESSAGE_TYPE_CHECK_CONNECTION\n");
|
|
break;
|
|
}
|
|
|
|
case profiler::net::MESSAGE_TYPE_REQUEST_MAIN_FRAME_TIME_MAX_AVG_US:
|
|
{
|
|
profiler::timestamp_t maxDuration = maxFrameDuration(), avgDuration = avgFrameDuration();
|
|
maxDuration = TICKS_TO_US(maxDuration);
|
|
avgDuration = TICKS_TO_US(avgDuration);
|
|
const profiler::net::TimestampMessage reply(profiler::net::MESSAGE_TYPE_REPLY_MAIN_FRAME_TIME_MAX_AVG_US, (uint32_t)maxDuration, (uint32_t)avgDuration);
|
|
bytes = socket.send(&reply, sizeof(profiler::net::TimestampMessage));
|
|
hasConnect = bytes > 0;
|
|
break;
|
|
}
|
|
|
|
case profiler::net::MESSAGE_TYPE_REQUEST_START_CAPTURE:
|
|
{
|
|
EASY_LOGMSG("receive REQUEST_START_CAPTURE\n");
|
|
|
|
::profiler::timestamp_t t = 0;
|
|
EASY_FORCE_EVENT(t, "StartCapture", EASY_COLOR_START, profiler::OFF);
|
|
|
|
m_dumpSpin.lock();
|
|
const auto prev = m_profilerStatus.exchange(EASY_PROF_ENABLED, std::memory_order_release);
|
|
if (prev != EASY_PROF_ENABLED) {
|
|
enableEventTracer();
|
|
m_beginTime = t;
|
|
}
|
|
m_dumpSpin.unlock();
|
|
|
|
replyMessage.type = profiler::net::MESSAGE_TYPE_REPLY_START_CAPTURING;
|
|
bytes = socket.send(&replyMessage, sizeof(replyMessage));
|
|
hasConnect = bytes > 0;
|
|
|
|
break;
|
|
}
|
|
|
|
case profiler::net::MESSAGE_TYPE_REQUEST_STOP_CAPTURE:
|
|
{
|
|
EASY_LOGMSG("receive REQUEST_STOP_CAPTURE\n");
|
|
|
|
m_dumpSpin.lock();
|
|
auto time = getCurrentTime();
|
|
const auto prev = m_profilerStatus.exchange(EASY_PROF_DUMP, std::memory_order_release);
|
|
if (prev == EASY_PROF_ENABLED) {
|
|
disableEventTracer();
|
|
m_endTime = time;
|
|
}
|
|
EASY_FORCE_EVENT2(m_endTime, "StopCapture", EASY_COLOR_END, profiler::OFF);
|
|
|
|
//TODO
|
|
//if connection aborted - ignore this part
|
|
|
|
profiler::OStream os;
|
|
dumpBlocksToStream(os, false);
|
|
m_dumpSpin.unlock();
|
|
|
|
const auto size = os.stream().tellp();
|
|
static const decltype(size) badSize = -1;
|
|
if (size != badSize)
|
|
{
|
|
const profiler::net::DataMessage dm(static_cast<uint32_t>(size), profiler::net::MESSAGE_TYPE_REPLY_BLOCKS);
|
|
|
|
const size_t packet_size = sizeof(dm) + dm.size;
|
|
std::string sendbuf;
|
|
sendbuf.reserve(packet_size + 1);
|
|
|
|
if (sendbuf.capacity() >= packet_size) // check if there is enough memory
|
|
{
|
|
sendbuf.append((const char*)&dm, sizeof(dm));
|
|
sendbuf += os.stream().str(); // TODO: Avoid double-coping data from stringstream!
|
|
os.clear();
|
|
|
|
bytes = socket.send(sendbuf.c_str(), packet_size);
|
|
hasConnect = bytes > 0;
|
|
}
|
|
else
|
|
{
|
|
EASY_ERROR("Can not send blocks. Not enough memory for allocating " << packet_size << " bytes");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EASY_ERROR("Can not send blocks. Bad std::stringstream.tellp() == -1");
|
|
}
|
|
|
|
replyMessage.type = profiler::net::MESSAGE_TYPE_REPLY_BLOCKS_END;
|
|
bytes = socket.send(&replyMessage, sizeof(replyMessage));
|
|
hasConnect = bytes > 0;
|
|
|
|
break;
|
|
}
|
|
|
|
case profiler::net::MESSAGE_TYPE_REQUEST_BLOCKS_DESCRIPTION:
|
|
{
|
|
EASY_LOGMSG("receive REQUEST_BLOCKS_DESCRIPTION\n");
|
|
|
|
profiler::OStream os;
|
|
|
|
// Write profiler signature and version
|
|
os.write(PROFILER_SIGNATURE);
|
|
os.write(EASY_CURRENT_VERSION);
|
|
|
|
// Write block descriptors
|
|
m_storedSpin.lock();
|
|
os.write(static_cast<uint32_t>(m_descriptors.size()));
|
|
os.write(m_usedMemorySize);
|
|
for (const auto descriptor : m_descriptors)
|
|
{
|
|
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);
|
|
|
|
os.write(size);
|
|
os.write<profiler::BaseBlockDescriptor>(*descriptor);
|
|
os.write(name_size);
|
|
os.write(descriptor->name(), name_size);
|
|
os.write(descriptor->filename(), filename_size);
|
|
}
|
|
m_storedSpin.unlock();
|
|
// END of Write block descriptors.
|
|
|
|
const auto size = os.stream().tellp();
|
|
static const decltype(size) badSize = -1;
|
|
if (size != badSize)
|
|
{
|
|
const profiler::net::DataMessage dm(static_cast<uint32_t>(size), profiler::net::MESSAGE_TYPE_REPLY_BLOCKS_DESCRIPTION);
|
|
|
|
const size_t packet_size = sizeof(dm) + dm.size;
|
|
std::string sendbuf;
|
|
sendbuf.reserve(packet_size + 1);
|
|
|
|
if (sendbuf.capacity() >= packet_size) // check if there is enough memory
|
|
{
|
|
sendbuf.append((const char*)&dm, sizeof(dm));
|
|
sendbuf += os.stream().str(); // TODO: Avoid double-coping data from stringstream!
|
|
os.clear();
|
|
|
|
bytes = socket.send(sendbuf.c_str(), packet_size);
|
|
hasConnect = bytes > 0;
|
|
}
|
|
else
|
|
{
|
|
EASY_ERROR("Can not send block descriptions. Not enough memory for allocating " << packet_size << " bytes");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EASY_ERROR("Can not send block descriptions. Bad std::stringstream.tellp() == -1");
|
|
}
|
|
|
|
replyMessage.type = profiler::net::MESSAGE_TYPE_REPLY_BLOCKS_DESCRIPTION_END;
|
|
bytes = socket.send(&replyMessage, sizeof(replyMessage));
|
|
hasConnect = bytes > 0;
|
|
|
|
break;
|
|
}
|
|
|
|
case profiler::net::MESSAGE_TYPE_EDIT_BLOCK_STATUS:
|
|
{
|
|
auto data = reinterpret_cast<const profiler::net::BlockStatusMessage*>(message);
|
|
|
|
EASY_LOGMSG("receive EDIT_BLOCK_STATUS id=" << data->id << " status=" << data->status << std::endl);
|
|
|
|
setBlockStatus(data->id, static_cast<::profiler::EasyBlockStatus>(data->status));
|
|
|
|
break;
|
|
}
|
|
|
|
case profiler::net::MESSAGE_TYPE_EVENT_TRACING_STATUS:
|
|
{
|
|
auto data = reinterpret_cast<const profiler::net::BoolMessage*>(message);
|
|
|
|
EASY_LOGMSG("receive EVENT_TRACING_STATUS on=" << data->flag << std::endl);
|
|
|
|
m_isEventTracingEnabled.store(data->flag, std::memory_order_release);
|
|
break;
|
|
}
|
|
|
|
case profiler::net::MESSAGE_TYPE_EVENT_TRACING_PRIORITY:
|
|
{
|
|
#if defined(_WIN32) || EASY_OPTION_LOG_ENABLED != 0
|
|
auto data = reinterpret_cast<const profiler::net::BoolMessage*>(message);
|
|
#endif
|
|
|
|
EASY_LOGMSG("receive EVENT_TRACING_PRIORITY low=" << data->flag << std::endl);
|
|
|
|
#ifdef _WIN32
|
|
EasyEventTracer::instance().setLowPriority(data->flag);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//nn_freemsg (buf);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|