diff --git a/easy_profiler_core/event_trace_status.h b/easy_profiler_core/event_trace_status.h index 4a9f42d..12c4476 100644 --- a/easy_profiler_core/event_trace_status.h +++ b/easy_profiler_core/event_trace_status.h @@ -1,27 +1,27 @@ - - - -#ifndef EASY_PROFILER__EVENT_TRACE_STATUS__H_ -#define EASY_PROFILER__EVENT_TRACE_STATUS__H_ - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -namespace profiler { - - enum EventTracingEnableStatus : unsigned char - { - EVENT_TRACING_LAUNCHED_SUCCESSFULLY = 0, - EVENT_TRACING_NOT_ENOUGH_ACCESS_RIGHTS, - EVENT_TRACING_WAS_LAUNCHED_BY_SOMEBODY_ELSE, - EVENT_TRACING_BAD_PROPERTIES_SIZE, - EVENT_TRACING_OPEN_TRACE_ERROR, - EVENT_TRACING_MISTERIOUS_ERROR, - }; - -} // END of namespace profiler. - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -#endif // EASY_PROFILER__EVENT_TRACE_STATUS__H_ + + + +#ifndef EASY_PROFILER__EVENT_TRACE_STATUS__H_ +#define EASY_PROFILER__EVENT_TRACE_STATUS__H_ + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +namespace profiler { + + enum EventTracingEnableStatus : unsigned char + { + EVENT_TRACING_LAUNCHED_SUCCESSFULLY = 0, + EVENT_TRACING_NOT_ENOUGH_ACCESS_RIGHTS, + EVENT_TRACING_WAS_LAUNCHED_BY_SOMEBODY_ELSE, + EVENT_TRACING_BAD_PROPERTIES_SIZE, + EVENT_TRACING_OPEN_TRACE_ERROR, + EVENT_TRACING_MISTERIOUS_ERROR, + }; + +} // END of namespace profiler. + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +#endif // EASY_PROFILER__EVENT_TRACE_STATUS__H_ diff --git a/easy_profiler_core/event_trace_win.cpp b/easy_profiler_core/event_trace_win.cpp index 2d80aea..81187f4 100644 --- a/easy_profiler_core/event_trace_win.cpp +++ b/easy_profiler_core/event_trace_win.cpp @@ -1,483 +1,483 @@ -/************************************************************************ -* file name : event_trace_win.cpp -* ----------------- : -* creation time : 2016/09/04 -* author : Victor Zarubkin -* email : v.s.zarubkin@gmail.com -* ----------------- : -* description : The file contains implementation of EasyEventTracer class used for tracing -* : Windows system events to get context switches. -* ----------------- : -* change log : * 2016/09/04 Victor Zarubkin: initial commit. -* : -* : * 2016/09/13 Victor Zarubkin: get process id and process name -* : of the owner of thread with id == CSwitch::NewThreadId. -* : -* : * 2016/09/17 Victor Zarubkin: added log messages printing. -* ----------------- : -* license : Lightweight profiler library for c++ -* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin -* : -* : -* : Licensed under 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. -* : -* : -* : GNU General Public License Usage -* : Alternatively, this file may be used 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 . -************************************************************************/ - -#ifdef _WIN32 -#include -#include -#include -#include "easy/profiler.h" -#include "profile_manager.h" -#include "current_time.h" - -#include "event_trace_win.h" -#include - -#if EASY_OPTION_LOG_ENABLED != 0 -# include -# ifndef EASY_ETW_LOG -# define EASY_ETW_LOG ::std::cerr -# endif -#endif - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -//extern ProfileManager& MANAGER; -#define MANAGER ProfileManager::instance() - -::std::atomic_uint64_t TRACING_END_TIME = ATOMIC_VAR_INIT(~0ULL); - -namespace profiler { - - const decltype(EVENT_DESCRIPTOR::Opcode) SWITCH_CONTEXT_OPCODE = 36; - const int RAW_TIMESTAMP_TIME_TYPE = 1; - - ////////////////////////////////////////////////////////////////////////// - - struct ProcessInfo { - std::string name; - processid_t id = 0; - int8_t valid = 0; - }; - - ////////////////////////////////////////////////////////////////////////// - - // CSwitch class - // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa964744(v=vs.85).aspx - // EventType = 36 - struct CSwitch - { - uint32_t NewThreadId; - uint32_t OldThreadId; - int8_t NewThreadPriority; - int8_t OldThreadPriority; - uint8_t PreviousCState; - int8_t SpareByte; - int8_t OldThreadWaitReason; - int8_t OldThreadWaitMode; - int8_t OldThreadState; - int8_t OldThreadWaitIdealProcessor; - uint32_t NewThreadWaitTime; - uint32_t Reserved; - }; - - ////////////////////////////////////////////////////////////////////////// - - typedef ::std::unordered_map thread_process_info_map; - typedef ::std::unordered_map process_info_map; - - // Using static is safe because processTraceEvent() is called from one thread - 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) - { - if (_traceEvent->EventHeader.EventDescriptor.Opcode != SWITCH_CONTEXT_OPCODE) - return; - - if (sizeof(CSwitch) != _traceEvent->UserDataLength) - return; - - EASY_FUNCTION(::profiler::colors::White, ::profiler::OFF); - - auto _contextSwitchEvent = reinterpret_cast(_traceEvent->UserData); - const auto time = static_cast<::profiler::timestamp_t>(_traceEvent->EventHeader.TimeStamp.QuadPart); - if (time > TRACING_END_TIME.load(::std::memory_order_acquire)) - return; - - processid_t pid = 0; - const char* process_name = ""; - - // Trying to get target process name and id - auto it = THREAD_PROCESS_INFO_TABLE.find(_contextSwitchEvent->NewThreadId); - if (it == THREAD_PROCESS_INFO_TABLE.end()) - { - auto hThread = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, _contextSwitchEvent->NewThreadId); - if (hThread != nullptr) - { - pid = GetProcessIdOfThread(hThread); - auto pinfo = &PROCESS_INFO_TABLE[pid]; - - if (pinfo->valid == 0) - { - if (pinfo->name.empty()) - { - static char numbuf[128] = {}; - sprintf(numbuf, "%u", pid); - pinfo->name = numbuf; - pinfo->id = pid; - } - - /* - According to documentation, using GetModuleBaseName() requires - PROCESS_QUERY_INFORMATION | PROCESS_VM_READ access rights. - But it works fine with PROCESS_QUERY_LIMITED_INFORMATION instead of PROCESS_QUERY_INFORMATION. - - See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683196(v=vs.85).aspx - */ - - //auto hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); - //if (hProc == nullptr) - auto hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, pid); - if (hProc != nullptr) - { - static TCHAR buf[MAX_PATH] = {}; // Using static is safe because processTraceEvent() is called from one thread - auto len = GetModuleBaseName(hProc, 0, buf, MAX_PATH); - - if (len != 0) - { - pinfo->name.reserve(pinfo->name.size() + 2 + len); - pinfo->name.append(" ", 1); - pinfo->name.append(buf, len); - pinfo->valid = 1; - } - - CloseHandle(hProc); - } - else - { - //auto err = GetLastError(); - //printf("OpenProcess(%u) fail: GetLastError() == %u\n", pid, err); - pinfo->valid = -1; - - if (pid == 4) { - pinfo->name.reserve(pinfo->name.size() + 8); - pinfo->name.append(" System", 7); - } - } - } - - process_name = pinfo->name.c_str(); - THREAD_PROCESS_INFO_TABLE[_contextSwitchEvent->NewThreadId] = pinfo; - - CloseHandle(hThread); - } - else - { - //printf("Can not OpenThread(%u);\n", _contextSwitchEvent->NewThreadId); - THREAD_PROCESS_INFO_TABLE[_contextSwitchEvent->NewThreadId] = nullptr; - } - } - else - { - auto pinfo = it->second; - if (pinfo != nullptr) - process_name = pinfo->name.c_str(); - else if (it->first == 0) - process_name = "System Idle"; - else if (it->first == 4) - process_name = "System"; - } - - MANAGER.beginContextSwitch(_contextSwitchEvent->OldThreadId, time, _contextSwitchEvent->NewThreadId, process_name); - MANAGER.endContextSwitch(_contextSwitchEvent->NewThreadId, pid, time); - } - - ////////////////////////////////////////////////////////////////////////// - -#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() - { - m_lowPriority = ATOMIC_VAR_INIT(EASY_OPTION_LOW_PRIORITY_EVENT_TRACING); - } - - EasyEventTracer::~EasyEventTracer() - { - disable(); - } - - bool EasyEventTracer::isLowPriority() const - { - return m_lowPriority.load(::std::memory_order_acquire); - } - - void EasyEventTracer::setLowPriority(bool _value) - { - m_lowPriority.store(_value, ::std::memory_order_release); - } - - bool setPrivilege(HANDLE hToken, LPCSTR _privelegeName) - { - bool success = false; - - if (hToken) - { - LUID privilegyId; - if (LookupPrivilegeValue(NULL, _privelegeName, &privilegyId)) - { - TOKEN_PRIVILEGES tokenPrivilege; - tokenPrivilege.PrivilegeCount = 1; - tokenPrivilege.Privileges[0].Luid = privilegyId; - tokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - success = AdjustTokenPrivileges(hToken, FALSE, &tokenPrivilege, sizeof(TOKEN_PRIVILEGES), NULL, NULL) != FALSE; - } - } - -#if EASY_OPTION_LOG_ENABLED != 0 - if (!success) - EASY_ETW_LOG << "Warning: EasyProfiler failed to set " << _privelegeName << " privelege for the application.\n"; -#endif - - return success; - } - - void EasyEventTracer::setProcessPrivileges() - { - static bool alreadySet = false; - if (alreadySet) - return; - - alreadySet = true; - - HANDLE hToken = nullptr; - if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) - { -#if EASY_OPTION_LOG_ENABLED != 0 - const bool success = setPrivilege(hToken, SE_DEBUG_NAME); - if (!success) - EASY_ETW_LOG << "Warning: Some context switch events could not get process name.\n"; -#else - setPrivilege(hToken, SE_DEBUG_NAME); -#endif - - CloseHandle(hToken); - } -#if EASY_OPTION_LOG_ENABLED != 0 - else - { - EASY_ETW_LOG << "Warning: EasyProfiler failed to open process to adjust priveleges.\n"; - } -#endif - } - - ::profiler::EventTracingEnableStatus EasyEventTracer::startTrace(bool _force, int _step) - { - auto startTraceResult = StartTrace(&m_sessionHandle, KERNEL_LOGGER_NAME, props()); - switch (startTraceResult) - { - case ERROR_SUCCESS: - return EVENT_TRACING_LAUNCHED_SUCCESSFULLY; - - case ERROR_ALREADY_EXISTS: - { - if (_force) - { - // Try to stop another event tracing session to force launch self session. - - if (_step == 0) - { - /* - According to https://msdn.microsoft.com/en-us/library/windows/desktop/aa363696(v=vs.85).aspx - SessionHandle is ignored (and could be NULL) if SessionName is not NULL, - and you only need to set the Wnode.BufferSize, Wnode.Guid, LoggerNameOffset, and LogFileNameOffset - in EVENT_TRACE_PROPERTIES structure if ControlCode is EVENT_TRACE_CONTROL_STOP. - All data is already set for m_properties to the moment. Simply copy m_properties and use the copy. - - This method supposed to be faster than launching console window and executing shell command, - but if that would not work, return to using shell command "logman stop". - */ - - // static is safe because we are guarded by spin-lock m_spin - static Properties p = ([]{ Properties prp; strncpy(prp.sessionName, KERNEL_LOGGER_NAME, sizeof(prp.sessionName)); return prp; })(); - p.base = m_properties.base; // Use copy of m_properties to make sure m_properties will not be changed - - // Stop another session - ControlTrace(NULL, KERNEL_LOGGER_NAME, reinterpret_cast(&p), EVENT_TRACE_CONTROL_STOP); - - // Console window variant: - //if (32 >= (int)ShellExecute(NULL, NULL, "logman", "stop \"" KERNEL_LOGGER_NAME "\" -ets", NULL, SW_HIDE)) - // return EVENT_TRACING_WAS_LAUNCHED_BY_SOMEBODY_ELSE; - } - - if (_step < 4) - { - // Command executed successfully. Wait for a few time until tracing session finish. - ::std::this_thread::sleep_for(::std::chrono::milliseconds(500)); - return startTrace(true, ++_step); - } - } - -#if EASY_OPTION_LOG_ENABLED != 0 - EASY_ETW_LOG << "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_OPTION_LOG_ENABLED != 0 - EASY_ETW_LOG << "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_OPTION_LOG_ENABLED != 0 - EASY_ETW_LOG << "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_OPTION_LOG_ENABLED != 0 - EASY_ETW_LOG << "Error: EasyProfiler.ETW not launched: StartTrace() returned " << startTraceResult << ::std::endl; -#endif - return EVENT_TRACING_MISTERIOUS_ERROR; - } - - ::profiler::EventTracingEnableStatus EasyEventTracer::enable(bool _force) - { - ::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). - */ - EasyEventTracer::setProcessPrivileges(); - - // Clear properties - memset(&m_properties, 0, sizeof(m_properties)); - m_properties.base.Wnode.BufferSize = sizeof(m_properties); - m_properties.base.Wnode.Flags = WNODE_FLAG_TRACED_GUID; - m_properties.base.Wnode.ClientContext = RAW_TIMESTAMP_TIME_TYPE; - m_properties.base.Wnode.Guid = SystemTraceControlGuid; - m_properties.base.LoggerNameOffset = sizeof(m_properties.base); - 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; - - memset(&m_trace, 0, sizeof(m_trace)); - m_trace.LoggerName = KERNEL_LOGGER_NAME; - m_trace.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP; - m_trace.EventRecordCallback = ::profiler::processTraceEvent; - - m_openedHandle = OpenTrace(&m_trace); - if (m_openedHandle == INVALID_PROCESSTRACE_HANDLE) - { -#if EASY_OPTION_LOG_ENABLED != 0 - EASY_ETW_LOG << "Error: EasyProfiler.ETW not launched: OpenTrace() returned invalid handle.\n"; -#endif - return EVENT_TRACING_OPEN_TRACE_ERROR; - } - - /* - Have to launch a thread to process events because according to MSDN documentation: - - The ProcessTrace function blocks the thread until it delivers all events, the BufferCallback function returns FALSE, - or you call CloseTrace. If the consumer is consuming events in real time, the ProcessTrace function returns after - the controller stops the trace session. (Note that there may be a several-second delay before the function returns.) - - https://msdn.microsoft.com/en-us/library/windows/desktop/aa364093(v=vs.85).aspx - */ - m_processThread = ::std::move(::std::thread([this]() - { - EASY_THREAD_SCOPE("EasyProfiler.ETW"); - ProcessTrace(&m_openedHandle, 1, 0, 0); - })); - - // Set low priority for event tracing thread - if (m_lowPriority.load(::std::memory_order_acquire)) - SetThreadPriority(m_processThread.native_handle(), THREAD_PRIORITY_LOWEST); - - m_bEnabled = true; - - return EVENT_TRACING_LAUNCHED_SUCCESSFULLY; - } - - void EasyEventTracer::disable() - { - ::profiler::guard_lock<::profiler::spin_lock> lock(m_spin); - if (!m_bEnabled) - return; - - TRACING_END_TIME.store(getCurrentTime(), ::std::memory_order_release); - - ControlTrace(m_openedHandle, KERNEL_LOGGER_NAME, props(), EVENT_TRACE_CONTROL_STOP); - CloseTrace(m_openedHandle); - - // Wait for ProcessTrace to finish to make sure no processTraceEvent() will be called later. - if (m_processThread.joinable()) - m_processThread.join(); - - m_bEnabled = false; - - // processTraceEvent() is not called anymore. Clean static maps is safe. - PROCESS_INFO_TABLE.clear(); - THREAD_PROCESS_INFO_TABLE.clear(); - THREAD_PROCESS_INFO_TABLE[0U] = nullptr; - - TRACING_END_TIME.store(~0ULL, ::std::memory_order_release); - } - -} // END of namespace profiler. - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -#endif // _WIN32 +/************************************************************************ +* file name : event_trace_win.cpp +* ----------------- : +* creation time : 2016/09/04 +* author : Victor Zarubkin +* email : v.s.zarubkin@gmail.com +* ----------------- : +* description : The file contains implementation of EasyEventTracer class used for tracing +* : Windows system events to get context switches. +* ----------------- : +* change log : * 2016/09/04 Victor Zarubkin: initial commit. +* : +* : * 2016/09/13 Victor Zarubkin: get process id and process name +* : of the owner of thread with id == CSwitch::NewThreadId. +* : +* : * 2016/09/17 Victor Zarubkin: added log messages printing. +* ----------------- : +* license : Lightweight profiler library for c++ +* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin +* : +* : +* : Licensed under 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. +* : +* : +* : GNU General Public License Usage +* : Alternatively, this file may be used 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 . +************************************************************************/ + +#ifdef _WIN32 +#include +#include +#include +#include "easy/profiler.h" +#include "profile_manager.h" +#include "current_time.h" + +#include "event_trace_win.h" +#include + +#if EASY_OPTION_LOG_ENABLED != 0 +# include +# ifndef EASY_ETW_LOG +# define EASY_ETW_LOG ::std::cerr +# endif +#endif + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +//extern ProfileManager& MANAGER; +#define MANAGER ProfileManager::instance() + +::std::atomic_uint64_t TRACING_END_TIME = ATOMIC_VAR_INIT(~0ULL); + +namespace profiler { + + const decltype(EVENT_DESCRIPTOR::Opcode) SWITCH_CONTEXT_OPCODE = 36; + const int RAW_TIMESTAMP_TIME_TYPE = 1; + + ////////////////////////////////////////////////////////////////////////// + + struct ProcessInfo { + std::string name; + processid_t id = 0; + int8_t valid = 0; + }; + + ////////////////////////////////////////////////////////////////////////// + + // CSwitch class + // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa964744(v=vs.85).aspx + // EventType = 36 + struct CSwitch + { + uint32_t NewThreadId; + uint32_t OldThreadId; + int8_t NewThreadPriority; + int8_t OldThreadPriority; + uint8_t PreviousCState; + int8_t SpareByte; + int8_t OldThreadWaitReason; + int8_t OldThreadWaitMode; + int8_t OldThreadState; + int8_t OldThreadWaitIdealProcessor; + uint32_t NewThreadWaitTime; + uint32_t Reserved; + }; + + ////////////////////////////////////////////////////////////////////////// + + typedef ::std::unordered_map thread_process_info_map; + typedef ::std::unordered_map process_info_map; + + // Using static is safe because processTraceEvent() is called from one thread + 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) + { + if (_traceEvent->EventHeader.EventDescriptor.Opcode != SWITCH_CONTEXT_OPCODE) + return; + + if (sizeof(CSwitch) != _traceEvent->UserDataLength) + return; + + EASY_FUNCTION(::profiler::colors::White, ::profiler::OFF); + + auto _contextSwitchEvent = reinterpret_cast(_traceEvent->UserData); + const auto time = static_cast<::profiler::timestamp_t>(_traceEvent->EventHeader.TimeStamp.QuadPart); + if (time > TRACING_END_TIME.load(::std::memory_order_acquire)) + return; + + processid_t pid = 0; + const char* process_name = ""; + + // Trying to get target process name and id + auto it = THREAD_PROCESS_INFO_TABLE.find(_contextSwitchEvent->NewThreadId); + if (it == THREAD_PROCESS_INFO_TABLE.end()) + { + auto hThread = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, _contextSwitchEvent->NewThreadId); + if (hThread != nullptr) + { + pid = GetProcessIdOfThread(hThread); + auto pinfo = &PROCESS_INFO_TABLE[pid]; + + if (pinfo->valid == 0) + { + if (pinfo->name.empty()) + { + static char numbuf[128] = {}; + sprintf(numbuf, "%u", pid); + pinfo->name = numbuf; + pinfo->id = pid; + } + + /* + According to documentation, using GetModuleBaseName() requires + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ access rights. + But it works fine with PROCESS_QUERY_LIMITED_INFORMATION instead of PROCESS_QUERY_INFORMATION. + + See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683196(v=vs.85).aspx + */ + + //auto hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + //if (hProc == nullptr) + auto hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, pid); + if (hProc != nullptr) + { + static TCHAR buf[MAX_PATH] = {}; // Using static is safe because processTraceEvent() is called from one thread + auto len = GetModuleBaseName(hProc, 0, buf, MAX_PATH); + + if (len != 0) + { + pinfo->name.reserve(pinfo->name.size() + 2 + len); + pinfo->name.append(" ", 1); + pinfo->name.append(buf, len); + pinfo->valid = 1; + } + + CloseHandle(hProc); + } + else + { + //auto err = GetLastError(); + //printf("OpenProcess(%u) fail: GetLastError() == %u\n", pid, err); + pinfo->valid = -1; + + if (pid == 4) { + pinfo->name.reserve(pinfo->name.size() + 8); + pinfo->name.append(" System", 7); + } + } + } + + process_name = pinfo->name.c_str(); + THREAD_PROCESS_INFO_TABLE[_contextSwitchEvent->NewThreadId] = pinfo; + + CloseHandle(hThread); + } + else + { + //printf("Can not OpenThread(%u);\n", _contextSwitchEvent->NewThreadId); + THREAD_PROCESS_INFO_TABLE[_contextSwitchEvent->NewThreadId] = nullptr; + } + } + else + { + auto pinfo = it->second; + if (pinfo != nullptr) + process_name = pinfo->name.c_str(); + else if (it->first == 0) + process_name = "System Idle"; + else if (it->first == 4) + process_name = "System"; + } + + MANAGER.beginContextSwitch(_contextSwitchEvent->OldThreadId, time, _contextSwitchEvent->NewThreadId, process_name); + MANAGER.endContextSwitch(_contextSwitchEvent->NewThreadId, pid, time); + } + + ////////////////////////////////////////////////////////////////////////// + +#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() + { + m_lowPriority = ATOMIC_VAR_INIT(EASY_OPTION_LOW_PRIORITY_EVENT_TRACING); + } + + EasyEventTracer::~EasyEventTracer() + { + disable(); + } + + bool EasyEventTracer::isLowPriority() const + { + return m_lowPriority.load(::std::memory_order_acquire); + } + + void EasyEventTracer::setLowPriority(bool _value) + { + m_lowPriority.store(_value, ::std::memory_order_release); + } + + bool setPrivilege(HANDLE hToken, LPCSTR _privelegeName) + { + bool success = false; + + if (hToken) + { + LUID privilegyId; + if (LookupPrivilegeValue(NULL, _privelegeName, &privilegyId)) + { + TOKEN_PRIVILEGES tokenPrivilege; + tokenPrivilege.PrivilegeCount = 1; + tokenPrivilege.Privileges[0].Luid = privilegyId; + tokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + success = AdjustTokenPrivileges(hToken, FALSE, &tokenPrivilege, sizeof(TOKEN_PRIVILEGES), NULL, NULL) != FALSE; + } + } + +#if EASY_OPTION_LOG_ENABLED != 0 + if (!success) + EASY_ETW_LOG << "Warning: EasyProfiler failed to set " << _privelegeName << " privelege for the application.\n"; +#endif + + return success; + } + + void EasyEventTracer::setProcessPrivileges() + { + static bool alreadySet = false; + if (alreadySet) + return; + + alreadySet = true; + + HANDLE hToken = nullptr; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) + { +#if EASY_OPTION_LOG_ENABLED != 0 + const bool success = setPrivilege(hToken, SE_DEBUG_NAME); + if (!success) + EASY_ETW_LOG << "Warning: Some context switch events could not get process name.\n"; +#else + setPrivilege(hToken, SE_DEBUG_NAME); +#endif + + CloseHandle(hToken); + } +#if EASY_OPTION_LOG_ENABLED != 0 + else + { + EASY_ETW_LOG << "Warning: EasyProfiler failed to open process to adjust priveleges.\n"; + } +#endif + } + + ::profiler::EventTracingEnableStatus EasyEventTracer::startTrace(bool _force, int _step) + { + auto startTraceResult = StartTrace(&m_sessionHandle, KERNEL_LOGGER_NAME, props()); + switch (startTraceResult) + { + case ERROR_SUCCESS: + return EVENT_TRACING_LAUNCHED_SUCCESSFULLY; + + case ERROR_ALREADY_EXISTS: + { + if (_force) + { + // Try to stop another event tracing session to force launch self session. + + if (_step == 0) + { + /* + According to https://msdn.microsoft.com/en-us/library/windows/desktop/aa363696(v=vs.85).aspx + SessionHandle is ignored (and could be NULL) if SessionName is not NULL, + and you only need to set the Wnode.BufferSize, Wnode.Guid, LoggerNameOffset, and LogFileNameOffset + in EVENT_TRACE_PROPERTIES structure if ControlCode is EVENT_TRACE_CONTROL_STOP. + All data is already set for m_properties to the moment. Simply copy m_properties and use the copy. + + This method supposed to be faster than launching console window and executing shell command, + but if that would not work, return to using shell command "logman stop". + */ + + // static is safe because we are guarded by spin-lock m_spin + static Properties p = ([]{ Properties prp; strncpy(prp.sessionName, KERNEL_LOGGER_NAME, sizeof(prp.sessionName)); return prp; })(); + p.base = m_properties.base; // Use copy of m_properties to make sure m_properties will not be changed + + // Stop another session + ControlTrace(NULL, KERNEL_LOGGER_NAME, reinterpret_cast(&p), EVENT_TRACE_CONTROL_STOP); + + // Console window variant: + //if (32 >= (int)ShellExecute(NULL, NULL, "logman", "stop \"" KERNEL_LOGGER_NAME "\" -ets", NULL, SW_HIDE)) + // return EVENT_TRACING_WAS_LAUNCHED_BY_SOMEBODY_ELSE; + } + + if (_step < 4) + { + // Command executed successfully. Wait for a few time until tracing session finish. + ::std::this_thread::sleep_for(::std::chrono::milliseconds(500)); + return startTrace(true, ++_step); + } + } + +#if EASY_OPTION_LOG_ENABLED != 0 + EASY_ETW_LOG << "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_OPTION_LOG_ENABLED != 0 + EASY_ETW_LOG << "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_OPTION_LOG_ENABLED != 0 + EASY_ETW_LOG << "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_OPTION_LOG_ENABLED != 0 + EASY_ETW_LOG << "Error: EasyProfiler.ETW not launched: StartTrace() returned " << startTraceResult << ::std::endl; +#endif + return EVENT_TRACING_MISTERIOUS_ERROR; + } + + ::profiler::EventTracingEnableStatus EasyEventTracer::enable(bool _force) + { + ::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). + */ + EasyEventTracer::setProcessPrivileges(); + + // Clear properties + memset(&m_properties, 0, sizeof(m_properties)); + m_properties.base.Wnode.BufferSize = sizeof(m_properties); + m_properties.base.Wnode.Flags = WNODE_FLAG_TRACED_GUID; + m_properties.base.Wnode.ClientContext = RAW_TIMESTAMP_TIME_TYPE; + m_properties.base.Wnode.Guid = SystemTraceControlGuid; + m_properties.base.LoggerNameOffset = sizeof(m_properties.base); + 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; + + memset(&m_trace, 0, sizeof(m_trace)); + m_trace.LoggerName = KERNEL_LOGGER_NAME; + m_trace.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP; + m_trace.EventRecordCallback = ::profiler::processTraceEvent; + + m_openedHandle = OpenTrace(&m_trace); + if (m_openedHandle == INVALID_PROCESSTRACE_HANDLE) + { +#if EASY_OPTION_LOG_ENABLED != 0 + EASY_ETW_LOG << "Error: EasyProfiler.ETW not launched: OpenTrace() returned invalid handle.\n"; +#endif + return EVENT_TRACING_OPEN_TRACE_ERROR; + } + + /* + Have to launch a thread to process events because according to MSDN documentation: + + The ProcessTrace function blocks the thread until it delivers all events, the BufferCallback function returns FALSE, + or you call CloseTrace. If the consumer is consuming events in real time, the ProcessTrace function returns after + the controller stops the trace session. (Note that there may be a several-second delay before the function returns.) + + https://msdn.microsoft.com/en-us/library/windows/desktop/aa364093(v=vs.85).aspx + */ + m_processThread = ::std::move(::std::thread([this]() + { + EASY_THREAD_SCOPE("EasyProfiler.ETW"); + ProcessTrace(&m_openedHandle, 1, 0, 0); + })); + + // Set low priority for event tracing thread + if (m_lowPriority.load(::std::memory_order_acquire)) + SetThreadPriority(m_processThread.native_handle(), THREAD_PRIORITY_LOWEST); + + m_bEnabled = true; + + return EVENT_TRACING_LAUNCHED_SUCCESSFULLY; + } + + void EasyEventTracer::disable() + { + ::profiler::guard_lock<::profiler::spin_lock> lock(m_spin); + if (!m_bEnabled) + return; + + TRACING_END_TIME.store(getCurrentTime(), ::std::memory_order_release); + + ControlTrace(m_openedHandle, KERNEL_LOGGER_NAME, props(), EVENT_TRACE_CONTROL_STOP); + CloseTrace(m_openedHandle); + + // Wait for ProcessTrace to finish to make sure no processTraceEvent() will be called later. + if (m_processThread.joinable()) + m_processThread.join(); + + m_bEnabled = false; + + // processTraceEvent() is not called anymore. Clean static maps is safe. + PROCESS_INFO_TABLE.clear(); + THREAD_PROCESS_INFO_TABLE.clear(); + THREAD_PROCESS_INFO_TABLE[0U] = nullptr; + + TRACING_END_TIME.store(~0ULL, ::std::memory_order_release); + } + +} // END of namespace profiler. + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +#endif // _WIN32 diff --git a/easy_profiler_core/event_trace_win.h b/easy_profiler_core/event_trace_win.h index 21c27f3..8c11b8e 100644 --- a/easy_profiler_core/event_trace_win.h +++ b/easy_profiler_core/event_trace_win.h @@ -1,120 +1,120 @@ -/************************************************************************ -* file name : event_trace_win.h -* ----------------- : -* creation time : 2016/09/04 -* author : Victor Zarubkin -* email : v.s.zarubkin@gmail.com -* ----------------- : -* description : The file contains declaration of EasyEventTracer class used for tracing -* : Windows system events to get context switches. -* ----------------- : -* change log : * 2016/09/04 Victor Zarubkin: initial commit. -* : -* : * -* ----------------- : -* license : Lightweight profiler library for c++ -* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin -* : -* : -* : Licensed under 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. -* : -* : -* : GNU General Public License Usage -* : Alternatively, this file may be used 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 . -************************************************************************/ - -#ifndef EASY_PROFILER__EVENT_TRACE_WINDOWS__H_ -#define EASY_PROFILER__EVENT_TRACE_WINDOWS__H_ -#ifdef _WIN32 - -#define INITGUID // This is to enable using SystemTraceControlGuid in evntrace.h. -#include -#include -#include -#include -#include -#include -#include -#include -#include "event_trace_status.h" -#include "spin_lock.h" - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -namespace profiler { - - class EasyEventTracer EASY_FINAL - { -#ifndef EASY_MAGIC_STATIC_CPP11 - friend class EasyEventTracerInstance; -#endif - -#pragma pack(push, 1) - struct Properties { - EVENT_TRACE_PROPERTIES base; - char sessionName[sizeof(KERNEL_LOGGER_NAME)]; - }; -#pragma pack(pop) - - ::std::thread m_processThread; - Properties m_properties; - EVENT_TRACE_LOGFILE m_trace; - ::profiler::spin_lock m_spin; - ::std::atomic_bool m_lowPriority; - TRACEHANDLE m_sessionHandle = INVALID_PROCESSTRACE_HANDLE; - TRACEHANDLE m_openedHandle = INVALID_PROCESSTRACE_HANDLE; - bool m_bEnabled = false; - - public: - - static EasyEventTracer& instance(); - ~EasyEventTracer(); - - bool isLowPriority() const; - - ::profiler::EventTracingEnableStatus enable(bool _force = false); - void disable(); - void setLowPriority(bool _value); - static void setProcessPrivileges(); - - private: - - EasyEventTracer(); - - inline EVENT_TRACE_PROPERTIES* props() - { - return reinterpret_cast(&m_properties); - } - - ::profiler::EventTracingEnableStatus startTrace(bool _force, int _step = 0); - - }; // END of class EasyEventTracer. - -} // END of namespace profiler. - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -#endif // _WIN32 -#endif // EASY_PROFILER__EVENT_TRACE_WINDOWS__H_ +/************************************************************************ +* file name : event_trace_win.h +* ----------------- : +* creation time : 2016/09/04 +* author : Victor Zarubkin +* email : v.s.zarubkin@gmail.com +* ----------------- : +* description : The file contains declaration of EasyEventTracer class used for tracing +* : Windows system events to get context switches. +* ----------------- : +* change log : * 2016/09/04 Victor Zarubkin: initial commit. +* : +* : * +* ----------------- : +* license : Lightweight profiler library for c++ +* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin +* : +* : +* : Licensed under 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. +* : +* : +* : GNU General Public License Usage +* : Alternatively, this file may be used 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 . +************************************************************************/ + +#ifndef EASY_PROFILER__EVENT_TRACE_WINDOWS__H_ +#define EASY_PROFILER__EVENT_TRACE_WINDOWS__H_ +#ifdef _WIN32 + +#define INITGUID // This is to enable using SystemTraceControlGuid in evntrace.h. +#include +#include +#include +#include +#include +#include +#include +#include +#include "event_trace_status.h" +#include "spin_lock.h" + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +namespace profiler { + + class EasyEventTracer EASY_FINAL + { +#ifndef EASY_MAGIC_STATIC_CPP11 + friend class EasyEventTracerInstance; +#endif + +#pragma pack(push, 1) + struct Properties { + EVENT_TRACE_PROPERTIES base; + char sessionName[sizeof(KERNEL_LOGGER_NAME)]; + }; +#pragma pack(pop) + + ::std::thread m_processThread; + Properties m_properties; + EVENT_TRACE_LOGFILE m_trace; + ::profiler::spin_lock m_spin; + ::std::atomic_bool m_lowPriority; + TRACEHANDLE m_sessionHandle = INVALID_PROCESSTRACE_HANDLE; + TRACEHANDLE m_openedHandle = INVALID_PROCESSTRACE_HANDLE; + bool m_bEnabled = false; + + public: + + static EasyEventTracer& instance(); + ~EasyEventTracer(); + + bool isLowPriority() const; + + ::profiler::EventTracingEnableStatus enable(bool _force = false); + void disable(); + void setLowPriority(bool _value); + static void setProcessPrivileges(); + + private: + + EasyEventTracer(); + + inline EVENT_TRACE_PROPERTIES* props() + { + return reinterpret_cast(&m_properties); + } + + ::profiler::EventTracingEnableStatus startTrace(bool _force, int _step = 0); + + }; // END of class EasyEventTracer. + +} // END of namespace profiler. + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +#endif // _WIN32 +#endif // EASY_PROFILER__EVENT_TRACE_WINDOWS__H_ diff --git a/easy_profiler_core/hashed_cstr.h b/easy_profiler_core/hashed_cstr.h index 28e2a15..6a214d5 100644 --- a/easy_profiler_core/hashed_cstr.h +++ b/easy_profiler_core/hashed_cstr.h @@ -1,17 +1,17 @@ -/************************************************************************ -* file name : hashed_str.h -* ----------------- : -* creation time : 2016/09/11 -* author : Victor Zarubkin -* email : v.s.zarubkin@gmail.com -* ----------------- : -* description : The file contains definition of C-strings with calculated hash-code. -* : These strings may be used as optimized keys for std::unordered_map. -* ----------------- : -* change log : * 2016/09/11 Victor Zarubkin: Initial commit. Moved sources from reader.cpp -* : -* : * -* ----------------- : +/************************************************************************ +* file name : hashed_str.h +* ----------------- : +* creation time : 2016/09/11 +* author : Victor Zarubkin +* email : v.s.zarubkin@gmail.com +* ----------------- : +* description : The file contains definition of C-strings with calculated hash-code. +* : These strings may be used as optimized keys for std::unordered_map. +* ----------------- : +* change log : * 2016/09/11 Victor Zarubkin: Initial commit. Moved sources from reader.cpp +* : +* : * +* ----------------- : * license : Lightweight profiler library for c++ * : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin * : @@ -26,248 +26,248 @@ * : 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 . -************************************************************************/ - -#ifndef EASY_PROFILER__HASHED_CSTR__H_ -#define EASY_PROFILER__HASHED_CSTR__H_ - -#include -#include -#include - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -#ifdef _WIN32 - -namespace profiler { - - /** \brief Simple C-string pointer with length. - - It is used as base class for a key in std::unordered_map. - It is used to get better performance than std::string. - It simply stores a pointer and a length, there is no - any memory allocation and copy. - - \warning Make sure you know what you are doing. You have to be sure that - pointed C-string will exist until you finish using this cstring. - - \ingroup profiler - */ - class cstring - { - protected: - - const char* m_str; - size_t m_len; - - public: - - cstring(const char* _str) : m_str(_str), m_len(strlen(_str)) - { - } - - cstring(const char* _str, size_t _len) : m_str(_str), m_len(_len) - { - } - - cstring(const cstring&) = default; - cstring& operator = (const cstring&) = default; - - inline bool operator == (const cstring& _other) const - { - return m_len == _other.m_len && !strncmp(m_str, _other.m_str, m_len); - } - - inline bool operator != (const cstring& _other) const - { - return !operator == (_other); - } - - inline bool operator < (const cstring& _other) const - { - if (m_len == _other.m_len) - { - return strncmp(m_str, _other.m_str, m_len) < 0; - } - - return m_len < _other.m_len; - } - - inline const char* c_str() const - { - return m_str; - } - - inline size_t size() const - { - return m_len; - } - - }; // END of class cstring. - - /** \brief cstring with precalculated hash. - - This is used to calculate hash for C-string and to cache it - to be used in the future without recurring hash calculatoin. - - \note This class is used as a key in std::unordered_map. - - \ingroup profiler - */ - class hashed_cstr : public cstring - { - typedef cstring Parent; - - size_t m_hash; - - public: - - hashed_cstr(const char* _str) : Parent(_str), m_hash(0) - { - m_hash = ::std::_Hash_seq((const unsigned char *)m_str, m_len); - } - - hashed_cstr(const char* _str, size_t _hash_code) : Parent(_str), m_hash(_hash_code) - { - } - - hashed_cstr(const char* _str, size_t _len, size_t _hash_code) : Parent(_str, _len), m_hash(_hash_code) - { - } - - hashed_cstr(const hashed_cstr&) = default; - hashed_cstr& operator = (const hashed_cstr&) = default; - - inline bool operator == (const hashed_cstr& _other) const - { - return m_hash == _other.m_hash && Parent::operator == (_other); - } - - inline bool operator != (const hashed_cstr& _other) const - { - return !operator == (_other); - } - - inline size_t hcode() const - { - return m_hash; - } - - }; // END of class hashed_cstr. - -} // END of namespace profiler. - -namespace std { - - /** \brief Simply returns precalculated hash of a C-string. */ - template <> struct hash<::profiler::hashed_cstr> { - typedef ::profiler::hashed_cstr argument_type; - typedef size_t result_type; - inline size_t operator () (const ::profiler::hashed_cstr& _str) const { - return _str.hcode(); - } - }; - -} // END of namespace std. - -#else //////////////////////////////////////////////////////////////////// - -// TODO: Create hashed_cstr for Linux (need to use Linux version of std::_Hash_seq) - -#endif - -namespace profiler { - - class hashed_stdstring - { - ::std::string m_str; - size_t m_hash; - - public: - - hashed_stdstring(const char* _str) : m_str(_str), m_hash(::std::hash<::std::string>()(m_str)) - { - } - - hashed_stdstring(const ::std::string& _str) : m_str(_str), m_hash(::std::hash<::std::string>()(m_str)) - { - } - - hashed_stdstring(::std::string&& _str) : m_str(::std::forward<::std::string&&>(_str)), m_hash(::std::hash<::std::string>()(m_str)) - { - } - - hashed_stdstring(hashed_stdstring&& _other) : m_str(::std::move(_other.m_str)), m_hash(_other.m_hash) - { - } - - hashed_stdstring(const char* _str, size_t _hash_code) : m_str(_str), m_hash(_hash_code) - { - } - - hashed_stdstring(const ::std::string& _str, size_t _hash_code) : m_str(_str), m_hash(_hash_code) - { - } - - hashed_stdstring(::std::string&& _str, size_t _hash_code) : m_str(::std::forward<::std::string&&>(_str)), m_hash(_hash_code) - { - } - - hashed_stdstring(const hashed_stdstring&) = default; - hashed_stdstring& operator = (const hashed_stdstring&) = default; - - hashed_stdstring& operator = (hashed_stdstring&& _other) - { - m_str = ::std::move(_other.m_str); - m_hash = _other.m_hash; - return *this; - } - - inline bool operator == (const hashed_stdstring& _other) const - { - return m_hash == _other.m_hash && m_str == _other.m_str; - } - - inline bool operator != (const hashed_stdstring& _other) const - { - return !operator == (_other); - } - - inline size_t hcode() const - { - return m_hash; - } - - inline const char* c_str() const - { - return m_str.c_str(); - } - - inline size_t size() const - { - return m_str.size(); - } - - }; // END of class hashed_stdstring. - -} // END of namespace profiler. - -namespace std { - - /** \brief Simply returns precalculated hash of a std::string. */ - template <> struct hash<::profiler::hashed_stdstring> { - typedef ::profiler::hashed_stdstring argument_type; - typedef size_t result_type; - inline size_t operator () (const ::profiler::hashed_stdstring& _str) const { - return _str.hcode(); - } - }; - -} // END of namespace std. - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -#endif // EASY_PROFILER__HASHED_CSTR__H_ +* : along with this program.If not, see . +************************************************************************/ + +#ifndef EASY_PROFILER__HASHED_CSTR__H_ +#define EASY_PROFILER__HASHED_CSTR__H_ + +#include +#include +#include + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +#ifdef _WIN32 + +namespace profiler { + + /** \brief Simple C-string pointer with length. + + It is used as base class for a key in std::unordered_map. + It is used to get better performance than std::string. + It simply stores a pointer and a length, there is no + any memory allocation and copy. + + \warning Make sure you know what you are doing. You have to be sure that + pointed C-string will exist until you finish using this cstring. + + \ingroup profiler + */ + class cstring + { + protected: + + const char* m_str; + size_t m_len; + + public: + + cstring(const char* _str) : m_str(_str), m_len(strlen(_str)) + { + } + + cstring(const char* _str, size_t _len) : m_str(_str), m_len(_len) + { + } + + cstring(const cstring&) = default; + cstring& operator = (const cstring&) = default; + + inline bool operator == (const cstring& _other) const + { + return m_len == _other.m_len && !strncmp(m_str, _other.m_str, m_len); + } + + inline bool operator != (const cstring& _other) const + { + return !operator == (_other); + } + + inline bool operator < (const cstring& _other) const + { + if (m_len == _other.m_len) + { + return strncmp(m_str, _other.m_str, m_len) < 0; + } + + return m_len < _other.m_len; + } + + inline const char* c_str() const + { + return m_str; + } + + inline size_t size() const + { + return m_len; + } + + }; // END of class cstring. + + /** \brief cstring with precalculated hash. + + This is used to calculate hash for C-string and to cache it + to be used in the future without recurring hash calculatoin. + + \note This class is used as a key in std::unordered_map. + + \ingroup profiler + */ + class hashed_cstr : public cstring + { + typedef cstring Parent; + + size_t m_hash; + + public: + + hashed_cstr(const char* _str) : Parent(_str), m_hash(0) + { + m_hash = ::std::_Hash_seq((const unsigned char *)m_str, m_len); + } + + hashed_cstr(const char* _str, size_t _hash_code) : Parent(_str), m_hash(_hash_code) + { + } + + hashed_cstr(const char* _str, size_t _len, size_t _hash_code) : Parent(_str, _len), m_hash(_hash_code) + { + } + + hashed_cstr(const hashed_cstr&) = default; + hashed_cstr& operator = (const hashed_cstr&) = default; + + inline bool operator == (const hashed_cstr& _other) const + { + return m_hash == _other.m_hash && Parent::operator == (_other); + } + + inline bool operator != (const hashed_cstr& _other) const + { + return !operator == (_other); + } + + inline size_t hcode() const + { + return m_hash; + } + + }; // END of class hashed_cstr. + +} // END of namespace profiler. + +namespace std { + + /** \brief Simply returns precalculated hash of a C-string. */ + template <> struct hash<::profiler::hashed_cstr> { + typedef ::profiler::hashed_cstr argument_type; + typedef size_t result_type; + inline size_t operator () (const ::profiler::hashed_cstr& _str) const { + return _str.hcode(); + } + }; + +} // END of namespace std. + +#else //////////////////////////////////////////////////////////////////// + +// TODO: Create hashed_cstr for Linux (need to use Linux version of std::_Hash_seq) + +#endif + +namespace profiler { + + class hashed_stdstring + { + ::std::string m_str; + size_t m_hash; + + public: + + hashed_stdstring(const char* _str) : m_str(_str), m_hash(::std::hash<::std::string>()(m_str)) + { + } + + hashed_stdstring(const ::std::string& _str) : m_str(_str), m_hash(::std::hash<::std::string>()(m_str)) + { + } + + hashed_stdstring(::std::string&& _str) : m_str(::std::forward<::std::string&&>(_str)), m_hash(::std::hash<::std::string>()(m_str)) + { + } + + hashed_stdstring(hashed_stdstring&& _other) : m_str(::std::move(_other.m_str)), m_hash(_other.m_hash) + { + } + + hashed_stdstring(const char* _str, size_t _hash_code) : m_str(_str), m_hash(_hash_code) + { + } + + hashed_stdstring(const ::std::string& _str, size_t _hash_code) : m_str(_str), m_hash(_hash_code) + { + } + + hashed_stdstring(::std::string&& _str, size_t _hash_code) : m_str(::std::forward<::std::string&&>(_str)), m_hash(_hash_code) + { + } + + hashed_stdstring(const hashed_stdstring&) = default; + hashed_stdstring& operator = (const hashed_stdstring&) = default; + + hashed_stdstring& operator = (hashed_stdstring&& _other) + { + m_str = ::std::move(_other.m_str); + m_hash = _other.m_hash; + return *this; + } + + inline bool operator == (const hashed_stdstring& _other) const + { + return m_hash == _other.m_hash && m_str == _other.m_str; + } + + inline bool operator != (const hashed_stdstring& _other) const + { + return !operator == (_other); + } + + inline size_t hcode() const + { + return m_hash; + } + + inline const char* c_str() const + { + return m_str.c_str(); + } + + inline size_t size() const + { + return m_str.size(); + } + + }; // END of class hashed_stdstring. + +} // END of namespace profiler. + +namespace std { + + /** \brief Simply returns precalculated hash of a std::string. */ + template <> struct hash<::profiler::hashed_stdstring> { + typedef ::profiler::hashed_stdstring argument_type; + typedef size_t result_type; + inline size_t operator () (const ::profiler::hashed_stdstring& _str) const { + return _str.hcode(); + } + }; + +} // END of namespace std. + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +#endif // EASY_PROFILER__HASHED_CSTR__H_ diff --git a/easy_profiler_core/outstream.h b/easy_profiler_core/outstream.h index edc7843..6ffc8e7 100644 --- a/easy_profiler_core/outstream.h +++ b/easy_profiler_core/outstream.h @@ -1,16 +1,16 @@ -/************************************************************************ -* file name : outstream.h -* ----------------- : -* creation time : 2016/09/11 -* authors : Sergey Yagovtsev, Victor Zarubkin -* emails : yse.sey@gmail.com, v.s.zarubkin@gmail.com -* ----------------- : -* description : The file contains definition of output stream helpers. -* ----------------- : -* change log : * 2016/09/11 Victor Zarubkin: Initial commit. Moved sources from profiler_manager.h/.cpp -* : -* : * -* ----------------- : +/************************************************************************ +* file name : outstream.h +* ----------------- : +* creation time : 2016/09/11 +* authors : Sergey Yagovtsev, Victor Zarubkin +* emails : yse.sey@gmail.com, v.s.zarubkin@gmail.com +* ----------------- : +* description : The file contains definition of output stream helpers. +* ----------------- : +* change log : * 2016/09/11 Victor Zarubkin: Initial commit. Moved sources from profiler_manager.h/.cpp +* : +* : * +* ----------------- : * license : Lightweight profiler library for c++ * : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin * : @@ -25,51 +25,51 @@ * : 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 . -************************************************************************/ - -#ifndef EASY_PROFILER__OUTPUT_STREAM__H_ -#define EASY_PROFILER__OUTPUT_STREAM__H_ - -#include -#include - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -namespace profiler { - - class OStream - { - ::std::stringstream m_stream; - - public: - - explicit OStream() : m_stream(std::ios_base::out | std::ios_base::binary) - { - - } - - template void write(const char* _data, T _size) - { - m_stream.write(_data, _size); - } - - template void write(const T& _data) - { - m_stream.write((const char*)&_data, sizeof(T)); - } - - const ::std::stringstream& stream() const - { - return m_stream; - } - - }; // END of class OStream. - -} // END of namespace profiler. - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -#endif // EASY_PROFILER__OUTPUT_STREAM__H_ +* : along with this program.If not, see . +************************************************************************/ + +#ifndef EASY_PROFILER__OUTPUT_STREAM__H_ +#define EASY_PROFILER__OUTPUT_STREAM__H_ + +#include +#include + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +namespace profiler { + + class OStream + { + ::std::stringstream m_stream; + + public: + + explicit OStream() : m_stream(std::ios_base::out | std::ios_base::binary) + { + + } + + template void write(const char* _data, T _size) + { + m_stream.write(_data, _size); + } + + template void write(const T& _data) + { + m_stream.write((const char*)&_data, sizeof(T)); + } + + const ::std::stringstream& stream() const + { + return m_stream; + } + + }; // END of class OStream. + +} // END of namespace profiler. + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +#endif // EASY_PROFILER__OUTPUT_STREAM__H_ diff --git a/profiler_gui/blocks_graphics_view.cpp b/profiler_gui/blocks_graphics_view.cpp index 3296ed9..3f3fdf2 100644 --- a/profiler_gui/blocks_graphics_view.cpp +++ b/profiler_gui/blocks_graphics_view.cpp @@ -1469,12 +1469,12 @@ void EasyGraphicsView::onIdleTimeout() { auto threadRoot = item->root(); - ::profiler::block_index_t ind = 0; - auto it = ::std::lower_bound(threadRoot->sync.begin(), threadRoot->sync.end(), itemBlock.node->begin(), [](::profiler::block_index_t _cs_index, ::profiler::timestamp_t _val) - { - return EASY_GLOBALS.gui_blocks[_cs_index].tree.node->begin() < _val; - }); - + ::profiler::block_index_t ind = 0; + auto it = ::std::lower_bound(threadRoot->sync.begin(), threadRoot->sync.end(), itemBlock.node->begin(), [](::profiler::block_index_t _cs_index, ::profiler::timestamp_t _val) + { + return EASY_GLOBALS.gui_blocks[_cs_index].tree.node->begin() < _val; + }); + if (it != threadRoot->sync.end()) { ind = it - threadRoot->sync.begin(); @@ -1486,20 +1486,20 @@ void EasyGraphicsView::onIdleTimeout() ind = static_cast<::profiler::block_index_t>(threadRoot->sync.size()); } - ::profiler::timestamp_t idleTime = 0; - for (::profiler::block_index_t ncs = static_cast<::profiler::block_index_t>(threadRoot->sync.size()); ind < ncs; ++ind) - { - auto cs_index = threadRoot->sync[ind]; - const auto cs = EASY_GLOBALS.gui_blocks[cs_index].tree.node; - - if (cs->begin() > itemBlock.node->end()) - break; - - if (itemBlock.node->begin() <= cs->begin() && cs->end() <= itemBlock.node->end()) - idleTime += cs->duration(); + ::profiler::timestamp_t idleTime = 0; + for (::profiler::block_index_t ncs = static_cast<::profiler::block_index_t>(threadRoot->sync.size()); ind < ncs; ++ind) + { + auto cs_index = threadRoot->sync[ind]; + const auto cs = EASY_GLOBALS.gui_blocks[cs_index].tree.node; + + if (cs->begin() > itemBlock.node->end()) + break; + + if (itemBlock.node->begin() <= cs->begin() && cs->end() <= itemBlock.node->end()) + idleTime += cs->duration(); } - auto active_time = duration - idleTime; + auto active_time = duration - idleTime; auto active_percent = duration == 0 ? 100. : ::profiler_gui::percentReal(active_time, duration); lay->addWidget(new QLabel("Active time:", widget), row, 0, Qt::AlignRight); lay->addWidget(new QLabel(QString("%1 (%2%)").arg(::profiler_gui::timeStringRealNs(EASY_GLOBALS.time_units, active_time, 3)).arg(QString::number(active_percent, 'g', 3)), widget), row, 1, 1, 3, Qt::AlignLeft); diff --git a/profiler_gui/blocks_tree_widget.cpp b/profiler_gui/blocks_tree_widget.cpp index 91f4e5b..8d74470 100644 --- a/profiler_gui/blocks_tree_widget.cpp +++ b/profiler_gui/blocks_tree_widget.cpp @@ -20,34 +20,34 @@ * : * : * 2016/08/18 Victor Zarubkin: Moved sources of TreeWidgetItem into tree_widget_item.h/.cpp * ----------------- : -* license : Lightweight profiler library for c++ -* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin -* : -* : -* : Licensed under 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. -* : -* : -* : GNU General Public License Usage -* : Alternatively, this file may be used 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 +* license : Lightweight profiler library for c++ +* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin +* : +* : +* : Licensed under 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. +* : +* : +* : GNU General Public License Usage +* : Alternatively, this file may be used 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 . ************************************************************************/ @@ -70,16 +70,16 @@ #include "blocks_tree_widget.h" #include "globals.h" -#ifdef _WIN32 -#include -#endif - -#ifdef max -#undef max -#endif - -#ifdef min -#undef min +#ifdef _WIN32 +#include +#endif + +#ifdef max +#undef max +#endif + +#ifdef min +#undef min #endif ////////////////////////////////////////////////////////////////////////// @@ -136,7 +136,7 @@ EasyTreeWidget::EasyTreeWidget(QWidget* _parent) setItemsExpandable(true); setAnimated(true); setSortingEnabled(false); - setColumnCount(COL_COLUMNS_NUMBER); + setColumnCount(COL_COLUMNS_NUMBER); auto header_item = new QTreeWidgetItem(); auto f = header()->font(); @@ -433,8 +433,8 @@ void EasyTreeWidget::clearSilent(bool _global) delete item; }, ::std::move(topLevelItems)); -#ifdef _WIN32 - SetThreadPriority(deleter_thread.native_handle(), THREAD_PRIORITY_LOWEST); +#ifdef _WIN32 + SetThreadPriority(deleter_thread.native_handle(), THREAD_PRIORITY_LOWEST); #endif deleter_thread.detach(); @@ -1087,8 +1087,8 @@ void EasyTreeWidget::loadSettings() memcpy(m_columnsHiddenStatus, byteArray.constData(), ::std::min(sizeof(m_columnsHiddenStatus), (size_t)byteArray.size())); } - auto state = settings.value("headerState").toByteArray(); - if (!state.isEmpty()) + auto state = settings.value("headerState").toByteArray(); + if (!state.isEmpty()) header()->restoreState(state); settings.endGroup(); @@ -1101,7 +1101,7 @@ void EasyTreeWidget::saveSettings() settings.setValue("color_rows", m_bColorRows); settings.setValue("regime", static_cast(m_mode)); settings.setValue("columns", QByteArray(m_columnsHiddenStatus, COL_COLUMNS_NUMBER)); - settings.setValue("headerState", header()->saveState()); + settings.setValue("headerState", header()->saveState()); settings.endGroup(); } @@ -1120,16 +1120,16 @@ EasyHierarchyWidget::EasyHierarchyWidget(QWidget* _parent) : Parent(_parent) m_searchBox->setFixedWidth(200); m_searchBox->setContentsMargins(5, 0, 0, 0); - QMenu* menu = new QMenu(this); - m_searchButton = menu->menuAction(); - m_searchButton->setText("Find next"); - m_searchButton->setIcon(QIcon(":/Search-next")); - m_searchButton->setData(true); - connect(m_searchButton, &QAction::triggered, this, &This::findNext); - - auto actionGroup = new QActionGroup(this); - actionGroup->setExclusive(true); - + QMenu* menu = new QMenu(this); + m_searchButton = menu->menuAction(); + m_searchButton->setText("Find next"); + m_searchButton->setIcon(QIcon(":/Search-next")); + m_searchButton->setData(true); + connect(m_searchButton, &QAction::triggered, this, &This::findNext); + + auto actionGroup = new QActionGroup(this); + actionGroup->setExclusive(true); + auto a = new QAction(tr("Find next"), actionGroup); a->setCheckable(true); a->setChecked(true); @@ -1139,18 +1139,18 @@ EasyHierarchyWidget::EasyHierarchyWidget(QWidget* _parent) : Parent(_parent) a = new QAction(tr("Find previous"), actionGroup); a->setCheckable(true); connect(a, &QAction::triggered, this, &This::findPrevFromMenu); - menu->addAction(a); - - menu->addSeparator(); - a = menu->addAction("Case sensitive"); + menu->addAction(a); + + menu->addSeparator(); + a = menu->addAction("Case sensitive"); a->setCheckable(true); a->setChecked(m_bCaseSensitiveSearch); connect(a, &QAction::triggered, [this](bool _checked){ m_bCaseSensitiveSearch = _checked; }); - menu->addAction(a); - - auto tb = new QToolBar(this); - tb->setIconSize(::profiler_gui::ICONS_SIZE); - tb->setContentsMargins(0, 0, 0, 0); + menu->addAction(a); + + auto tb = new QToolBar(this); + tb->setIconSize(::profiler_gui::ICONS_SIZE); + tb->setContentsMargins(0, 0, 0, 0); tb->addAction(m_searchButton); tb->addWidget(m_searchBox); diff --git a/profiler_gui/common_types.h b/profiler_gui/common_types.h index b69f325..3ab28d1 100644 --- a/profiler_gui/common_types.h +++ b/profiler_gui/common_types.h @@ -1,17 +1,17 @@ -/************************************************************************ -* file name : common_types.h -* ----------------- : -* creation time : 2016/07/31 -* author : Victor Zarubkin -* email : v.s.zarubkin@gmail.com -* ----------------- : -* description : The file contains declaration of common types for both GraphicsView -* : and TreeWidget. -* ----------------- : -* change log : * 2016/07/31 Victor Zarubkin: initial commit. -* : -* : * -* ----------------- : +/************************************************************************ +* file name : common_types.h +* ----------------- : +* creation time : 2016/07/31 +* author : Victor Zarubkin +* email : v.s.zarubkin@gmail.com +* ----------------- : +* description : The file contains declaration of common types for both GraphicsView +* : and TreeWidget. +* ----------------- : +* change log : * 2016/07/31 Victor Zarubkin: initial commit. +* : +* : * +* ----------------- : * license : Lightweight profiler library for c++ * : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin * : @@ -26,7 +26,7 @@ * : 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 . +* : along with this program.If not, see . ************************************************************************/ #ifndef EASY_PROFILER__GUI_COMMON_TYPES_H diff --git a/profiler_gui/descriptors_tree_widget.cpp b/profiler_gui/descriptors_tree_widget.cpp index ce60e76..737665a 100644 --- a/profiler_gui/descriptors_tree_widget.cpp +++ b/profiler_gui/descriptors_tree_widget.cpp @@ -12,34 +12,34 @@ * : * : * * ----------------- : -* license : Lightweight profiler library for c++ -* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin -* : -* : -* : Licensed under 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. -* : -* : -* : GNU General Public License Usage -* : Alternatively, this file may be used 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 +* license : Lightweight profiler library for c++ +* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin +* : +* : +* : Licensed under 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. +* : +* : +* : GNU General Public License Usage +* : Alternatively, this file may be used 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 . ************************************************************************/ @@ -62,16 +62,16 @@ #include "descriptors_tree_widget.h" #include "globals.h" -#ifdef _WIN32 -#include -#endif - -#ifdef max -#undef max -#endif - -#ifdef min -#undef min +#ifdef _WIN32 +#include +#endif + +#ifdef max +#undef max +#endif + +#ifdef min +#undef min #endif ////////////////////////////////////////////////////////////////////////// @@ -92,23 +92,23 @@ enum DescColumns { switch (_status) { - case ::profiler::OFF: - return ::profiler::ON; - - case ::profiler::ON: - return ::profiler::FORCE_ON; - - case ::profiler::FORCE_ON: - return ::profiler::OFF_RECURSIVE; - - case ::profiler::OFF_RECURSIVE: - return ::profiler::ON_WITHOUT_CHILDREN; - - case ::profiler::ON_WITHOUT_CHILDREN: - return ::profiler::FORCE_ON_WITHOUT_CHILDREN; - - case ::profiler::FORCE_ON_WITHOUT_CHILDREN: - return ::profiler::OFF; + case ::profiler::OFF: + return ::profiler::ON; + + case ::profiler::ON: + return ::profiler::FORCE_ON; + + case ::profiler::FORCE_ON: + return ::profiler::OFF_RECURSIVE; + + case ::profiler::OFF_RECURSIVE: + return ::profiler::ON_WITHOUT_CHILDREN; + + case ::profiler::ON_WITHOUT_CHILDREN: + return ::profiler::FORCE_ON_WITHOUT_CHILDREN; + + case ::profiler::FORCE_ON_WITHOUT_CHILDREN: + return ::profiler::OFF; } return ::profiler::OFF; @@ -118,23 +118,23 @@ const char* statusText(::profiler::EasyBlockStatus _status) { switch (_status) { - case ::profiler::OFF: - return "OFF"; - - case ::profiler::ON: - return "ON"; - - case ::profiler::FORCE_ON: - return "FORCE_ON"; - - case ::profiler::OFF_RECURSIVE: - return "OFF_RECURSIVE"; - - case ::profiler::ON_WITHOUT_CHILDREN: - return "ON_WITHOUT_CHILDREN"; - - case ::profiler::FORCE_ON_WITHOUT_CHILDREN: - return "FORCE_ON_WITHOUT_CHILDREN"; + case ::profiler::OFF: + return "OFF"; + + case ::profiler::ON: + return "ON"; + + case ::profiler::FORCE_ON: + return "FORCE_ON"; + + case ::profiler::OFF_RECURSIVE: + return "OFF_RECURSIVE"; + + case ::profiler::ON_WITHOUT_CHILDREN: + return "ON_WITHOUT_CHILDREN"; + + case ::profiler::FORCE_ON_WITHOUT_CHILDREN: + return "FORCE_ON_WITHOUT_CHILDREN"; } return ""; @@ -144,23 +144,23 @@ const char* statusText(::profiler::EasyBlockStatus _status) { switch (_status) { - case ::profiler::OFF: - return ::profiler::colors::Red900; - - case ::profiler::ON: - return ::profiler::colors::LightGreen900; - - case ::profiler::FORCE_ON: - return ::profiler::colors::LightGreen900; - - case ::profiler::OFF_RECURSIVE: - return ::profiler::colors::Red900; - - case ::profiler::ON_WITHOUT_CHILDREN: - return ::profiler::colors::Lime900; - - case ::profiler::FORCE_ON_WITHOUT_CHILDREN: - return ::profiler::colors::Lime900; + case ::profiler::OFF: + return ::profiler::colors::Red900; + + case ::profiler::ON: + return ::profiler::colors::LightGreen900; + + case ::profiler::FORCE_ON: + return ::profiler::colors::LightGreen900; + + case ::profiler::OFF_RECURSIVE: + return ::profiler::colors::Red900; + + case ::profiler::ON_WITHOUT_CHILDREN: + return ::profiler::colors::Lime900; + + case ::profiler::FORCE_ON_WITHOUT_CHILDREN: + return ::profiler::colors::Lime900; } return ::profiler::colors::Black; @@ -338,8 +338,8 @@ void EasyDescTreeWidget::clearSilent(bool _global) delete item; }, ::std::move(topLevelItems)); -#ifdef _WIN32 - SetThreadPriority(deleter_thread.native_handle(), THREAD_PRIORITY_LOWEST); +#ifdef _WIN32 + SetThreadPriority(deleter_thread.native_handle(), THREAD_PRIORITY_LOWEST); #endif deleter_thread.detach(); @@ -734,16 +734,16 @@ EasyDescWidget::EasyDescWidget(QWidget* _parent) : Parent(_parent) - QMenu* menu = new QMenu(this); - m_searchButton = menu->menuAction(); - m_searchButton->setText("Find next"); - m_searchButton->setIcon(QIcon(":/Search-next")); - m_searchButton->setData(true); - connect(m_searchButton, &QAction::triggered, this, &This::findNext); - - auto actionGroup = new QActionGroup(this); - actionGroup->setExclusive(true); - + QMenu* menu = new QMenu(this); + m_searchButton = menu->menuAction(); + m_searchButton->setText("Find next"); + m_searchButton->setIcon(QIcon(":/Search-next")); + m_searchButton->setData(true); + connect(m_searchButton, &QAction::triggered, this, &This::findNext); + + auto actionGroup = new QActionGroup(this); + actionGroup->setExclusive(true); + auto a = new QAction(tr("Find next"), actionGroup); a->setCheckable(true); a->setChecked(true); @@ -753,15 +753,15 @@ EasyDescWidget::EasyDescWidget(QWidget* _parent) : Parent(_parent) a = new QAction(tr("Find previous"), actionGroup); a->setCheckable(true); connect(a, &QAction::triggered, this, &This::findPrevFromMenu); - menu->addAction(a); - - menu->addSeparator(); - a = menu->addAction("Case sensitive"); + menu->addAction(a); + + menu->addSeparator(); + a = menu->addAction("Case sensitive"); a->setCheckable(true); a->setChecked(m_bCaseSensitiveSearch); connect(a, &QAction::triggered, [this](bool _checked){ m_bCaseSensitiveSearch = _checked; }); - menu->addAction(a); - + menu->addAction(a); + tb->addSeparator(); tb->addAction(m_searchButton); tb->addWidget(m_searchBox); diff --git a/profiler_gui/easy_graphics_scrollbar.cpp b/profiler_gui/easy_graphics_scrollbar.cpp index e0e75e7..6526922 100644 --- a/profiler_gui/easy_graphics_scrollbar.cpp +++ b/profiler_gui/easy_graphics_scrollbar.cpp @@ -772,7 +772,7 @@ void EasyHystogramItem::setSource(::profiler::thread_id_t _thread_id, const ::pr else { const auto& root = EASY_GLOBALS.profiler_blocks[_thread_id]; - m_threadName = ::profiler_gui::decoratedThreadName(EASY_GLOBALS.use_decorated_thread_name, root); + m_threadName = ::profiler_gui::decoratedThreadName(EASY_GLOBALS.use_decorated_thread_name, root); if (root.children.empty()) m_threadDuration = 0; diff --git a/profiler_gui/icons/collapse.svg b/profiler_gui/icons/collapse.svg index 21f1502..a51ddf5 100644 --- a/profiler_gui/icons/collapse.svg +++ b/profiler_gui/icons/collapse.svg @@ -1,44 +1,44 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/colors-black.svg b/profiler_gui/icons/colors-black.svg index 09398d9..7b2c3b4 100644 --- a/profiler_gui/icons/colors-black.svg +++ b/profiler_gui/icons/colors-black.svg @@ -1,59 +1,59 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/colors.svg b/profiler_gui/icons/colors.svg index 9cd72c9..c352876 100644 --- a/profiler_gui/icons/colors.svg +++ b/profiler_gui/icons/colors.svg @@ -1,73 +1,73 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/delete.svg b/profiler_gui/icons/delete.svg index 4090c77..9b06500 100644 --- a/profiler_gui/icons/delete.svg +++ b/profiler_gui/icons/delete.svg @@ -1,45 +1,45 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/expand.svg b/profiler_gui/icons/expand.svg index 2cd7796..8578b47 100644 --- a/profiler_gui/icons/expand.svg +++ b/profiler_gui/icons/expand.svg @@ -1,43 +1,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/lan.svg b/profiler_gui/icons/lan.svg index 069eb1c..a5fa668 100644 --- a/profiler_gui/icons/lan.svg +++ b/profiler_gui/icons/lan.svg @@ -1,58 +1,58 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/lan_on.svg b/profiler_gui/icons/lan_on.svg index e4ea6a1..53d26d4 100644 --- a/profiler_gui/icons/lan_on.svg +++ b/profiler_gui/icons/lan_on.svg @@ -1,58 +1,58 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/list.svg b/profiler_gui/icons/list.svg index af6bf94..11d825f 100644 --- a/profiler_gui/icons/list.svg +++ b/profiler_gui/icons/list.svg @@ -1,63 +1,63 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/logo.svg b/profiler_gui/icons/logo.svg index e7f39a8..eda0c91 100644 --- a/profiler_gui/icons/logo.svg +++ b/profiler_gui/icons/logo.svg @@ -1,51 +1,51 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/off.svg b/profiler_gui/icons/off.svg index dd5cc6e..61af352 100644 --- a/profiler_gui/icons/off.svg +++ b/profiler_gui/icons/off.svg @@ -1,46 +1,46 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/open-folder.svg b/profiler_gui/icons/open-folder.svg index 416bf57..b49800d 100644 --- a/profiler_gui/icons/open-folder.svg +++ b/profiler_gui/icons/open-folder.svg @@ -1,42 +1,42 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/open-folder2.svg b/profiler_gui/icons/open-folder2.svg index b6454a9..5d28eae 100644 --- a/profiler_gui/icons/open-folder2.svg +++ b/profiler_gui/icons/open-folder2.svg @@ -1,44 +1,44 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/play.svg b/profiler_gui/icons/play.svg index 256e444..64babea 100644 --- a/profiler_gui/icons/play.svg +++ b/profiler_gui/icons/play.svg @@ -1,43 +1,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/reload-folder2.svg b/profiler_gui/icons/reload-folder2.svg index d3f047c..f06e608 100644 --- a/profiler_gui/icons/reload-folder2.svg +++ b/profiler_gui/icons/reload-folder2.svg @@ -1,52 +1,52 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/reload.svg b/profiler_gui/icons/reload.svg index 18aac75..46a2732 100644 --- a/profiler_gui/icons/reload.svg +++ b/profiler_gui/icons/reload.svg @@ -1,75 +1,75 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/search-next.svg b/profiler_gui/icons/search-next.svg index a1f3ae7..a8cb193 100644 --- a/profiler_gui/icons/search-next.svg +++ b/profiler_gui/icons/search-next.svg @@ -1,57 +1,57 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/search-prev.svg b/profiler_gui/icons/search-prev.svg index b629110..4875b06 100644 --- a/profiler_gui/icons/search-prev.svg +++ b/profiler_gui/icons/search-prev.svg @@ -1,57 +1,57 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/settings.svg b/profiler_gui/icons/settings.svg index 07714c0..9f71600 100644 --- a/profiler_gui/icons/settings.svg +++ b/profiler_gui/icons/settings.svg @@ -1,70 +1,70 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/statistics.svg b/profiler_gui/icons/statistics.svg index 2d94ef2..3fcc347 100644 --- a/profiler_gui/icons/statistics.svg +++ b/profiler_gui/icons/statistics.svg @@ -1,47 +1,47 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/statistics2.svg b/profiler_gui/icons/statistics2.svg index 0943c0b..10ee74f 100644 --- a/profiler_gui/icons/statistics2.svg +++ b/profiler_gui/icons/statistics2.svg @@ -1,48 +1,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/stop.svg b/profiler_gui/icons/stop.svg index 964b50d..b6f3c5a 100644 --- a/profiler_gui/icons/stop.svg +++ b/profiler_gui/icons/stop.svg @@ -1,43 +1,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/wifi.svg b/profiler_gui/icons/wifi.svg index 6279e63..fed86c8 100644 --- a/profiler_gui/icons/wifi.svg +++ b/profiler_gui/icons/wifi.svg @@ -1,46 +1,46 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/icons/wifi_on.svg b/profiler_gui/icons/wifi_on.svg index e1e9146..ee4dd4c 100644 --- a/profiler_gui/icons/wifi_on.svg +++ b/profiler_gui/icons/wifi_on.svg @@ -1,46 +1,46 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/profiler_gui/main_window.cpp b/profiler_gui/main_window.cpp index 46635f9..1108d06 100644 --- a/profiler_gui/main_window.cpp +++ b/profiler_gui/main_window.cpp @@ -1,2245 +1,2245 @@ -/************************************************************************ -* file name : main_window.cpp -* ----------------- : -* creation time : 2016/06/26 -* author : Victor Zarubkin -* email : v.s.zarubkin@gmail.com -* ----------------- : -* description : The file contains implementation of MainWindow for easy_profiler GUI. -* ----------------- : -* change log : * 2016/06/26 Victor Zarubkin: Initial commit. -* : -* : * 2016/06/27 Victor Zarubkin: Passing blocks number to EasyTreeWidget::setTree(). -* : -* : * 2016/06/29 Victor Zarubkin: Added menu with tests. -* : -* : * 2016/06/30 Sergey Yagovtsev: Open file by command line argument -* : -* : * -* ----------------- : -* license : Lightweight profiler library for c++ -* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin -* : -* : -* : Licensed under 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. -* : -* : -* : GNU General Public License Usage -* : Alternatively, this file may be used 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 . -************************************************************************/ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "main_window.h" -#include "blocks_tree_widget.h" -#include "blocks_graphics_view.h" -#include "descriptors_tree_widget.h" -#include "globals.h" -#include "easy/easy_net.h" - -#ifdef max -#undef max -#endif - -#ifdef min -#undef min -#endif - -////////////////////////////////////////////////////////////////////////// - -const int LOADER_TIMER_INTERVAL = 40; -const auto NETWORK_CACHE_FILE = "easy_profiler_stream.cache"; - -////////////////////////////////////////////////////////////////////////// - -inline void clear_stream(std::stringstream& _stream) -{ -#if defined(__GNUC__) && __GNUC__ < 5 - // gcc 4 has a known bug which has been solved in gcc 5: - // std::stringstream has no swap() method :( - _stream.str(std::string()); -#else - std::stringstream().swap(_stream); -#endif -} - -////////////////////////////////////////////////////////////////////////// - -EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("localhost"), m_lastPort(::profiler::DEFAULT_PORT) -{ - { QIcon icon(":/logo"); if (!icon.isNull()) QApplication::setWindowIcon(icon); } - - setObjectName("ProfilerGUI_MainWindow"); - setWindowTitle("EasyProfiler"); - setDockNestingEnabled(true); - setAcceptDrops(true); - resize(800, 600); - - setStatusBar(new QStatusBar()); - - m_graphicsView = new QDockWidget("Diagram", this); - m_graphicsView->setObjectName("ProfilerGUI_Diagram"); - m_graphicsView->setMinimumHeight(50); - m_graphicsView->setAllowedAreas(Qt::AllDockWidgetAreas); - - auto graphicsView = new EasyGraphicsViewWidget(this); - m_graphicsView->setWidget(graphicsView); - - m_treeWidget = new QDockWidget("Hierarchy", this); - m_treeWidget->setObjectName("ProfilerGUI_Hierarchy"); - m_treeWidget->setMinimumHeight(50); - m_treeWidget->setAllowedAreas(Qt::AllDockWidgetAreas); - - auto treeWidget = new EasyHierarchyWidget(this); - m_treeWidget->setWidget(treeWidget); - - addDockWidget(Qt::TopDockWidgetArea, m_graphicsView); - addDockWidget(Qt::BottomDockWidgetArea, m_treeWidget); - -#if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 - auto descTree = new EasyDescWidget(); - m_descTreeWidget = new QDockWidget("Blocks"); - m_descTreeWidget->setObjectName("ProfilerGUI_Blocks"); - m_descTreeWidget->setMinimumHeight(50); - m_descTreeWidget->setAllowedAreas(Qt::AllDockWidgetAreas); - m_descTreeWidget->setWidget(descTree); - addDockWidget(Qt::BottomDockWidgetArea, m_descTreeWidget); -#endif - - - loadSettings(); - - - auto toolbar = addToolBar("FileToolbar"); - toolbar->setIconSize(::profiler_gui::ICONS_SIZE); - toolbar->setObjectName("ProfilerGUI_FileToolbar"); - toolbar->setContentsMargins(1, 0, 1, 0); - - m_loadActionMenu = new QMenu(this); - auto action = m_loadActionMenu->menuAction(); - action->setText("Open file"); - action->setIcon(QIcon(":/Open")); - connect(action, &QAction::triggered, this, &This::onOpenFileClicked); - toolbar->addAction(action); - - for (const auto& f : m_lastFiles) - { - action = new QAction(f, this); - connect(action, &QAction::triggered, this, &This::onOpenFileClicked); - m_loadActionMenu->addAction(action); - } - - m_saveAction = toolbar->addAction(QIcon(":/Save"), tr("Save"), this, SLOT(onSaveFileClicked(bool))); - m_deleteAction = toolbar->addAction(QIcon(":/Delete"), tr("Clear all"), this, SLOT(onDeleteClicked(bool))); - - m_saveAction->setEnabled(false); - m_deleteAction->setEnabled(false); - - - - toolbar = addToolBar("ProfileToolbar"); - toolbar->setIconSize(::profiler_gui::ICONS_SIZE); - toolbar->setObjectName("ProfilerGUI_ProfileToolbar"); - toolbar->setContentsMargins(1, 0, 1, 0); - - toolbar->addAction(QIcon(":/List"), tr("Blocks"), this, SLOT(onEditBlocksClicked(bool))); - m_captureAction = toolbar->addAction(QIcon(":/Start"), tr("Capture"), this, SLOT(onCaptureClicked(bool))); - m_captureAction->setEnabled(false); - - toolbar->addSeparator(); - m_connectAction = toolbar->addAction(QIcon(":/Connection"), tr("Connect"), this, SLOT(onConnectClicked(bool))); - - auto lbl = new QLabel("Address:", toolbar); - lbl->setContentsMargins(5, 0, 2, 0); - toolbar->addWidget(lbl); - m_addressEdit = new QLineEdit(); - m_addressEdit->setToolTip("Enter IP-address or host name"); - //QRegExp rx("^0*(2(5[0-5]|[0-4]\\d)|1?\\d{1,2})(\\.0*(2(5[0-5]|[0-4]\\d)|1?\\d{1,2})){3}$"); - //m_addressEdit->setValidator(new QRegExpValidator(rx, m_addressEdit)); - m_addressEdit->setText(m_lastAddress); - m_addressEdit->setFixedWidth((m_addressEdit->fontMetrics().width(QString("255.255.255.255")) * 3) / 2); - toolbar->addWidget(m_addressEdit); - - lbl = new QLabel("Port:", toolbar); - lbl->setContentsMargins(5, 0, 2, 0); - toolbar->addWidget(lbl); - m_portEdit = new QLineEdit(); - m_portEdit->setValidator(new QIntValidator(1, 65535, m_portEdit)); - m_portEdit->setText(QString::number(m_lastPort)); - m_portEdit->setFixedWidth(m_portEdit->fontMetrics().width(QString("000000")) + 10); - toolbar->addWidget(m_portEdit); - - connect(m_addressEdit, &QLineEdit::returnPressed, [this](){ onConnectClicked(true); }); - connect(m_portEdit, &QLineEdit::returnPressed, [this](){ onConnectClicked(true); }); - - - - toolbar = addToolBar("SetupToolbar"); - toolbar->setIconSize(::profiler_gui::ICONS_SIZE); - toolbar->setObjectName("ProfilerGUI_SetupToolbar"); - toolbar->setContentsMargins(1, 0, 1, 0); - - toolbar->addAction(QIcon(":/Expand"), "Expand all", this, SLOT(onExpandAllClicked(bool))); - toolbar->addAction(QIcon(":/Collapse"), "Collapse all", this, SLOT(onCollapseAllClicked(bool))); - - toolbar->addSeparator(); - auto menu = new QMenu("Settings", this); - QToolButton* toolButton = new QToolButton(toolbar); - toolButton->setIcon(QIcon(":/Settings")); - toolButton->setMenu(menu); - toolButton->setPopupMode(QToolButton::InstantPopup); - toolbar->addWidget(toolButton); - - action = menu->addAction("Statistics enabled"); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.enable_statistics); - connect(action, &QAction::triggered, this, &This::onEnableDisableStatistics); - if (EASY_GLOBALS.enable_statistics) - { - auto f = action->font(); - f.setBold(true); - action->setFont(f); - SET_ICON(action, ":/Stats"); - } - else - { - action->setText("Statistics disabled"); - SET_ICON(action, ":/Stats-off"); - } - - - menu->addSeparator(); - auto submenu = menu->addMenu("View"); - submenu->setToolTipsVisible(true); - action = submenu->addAction("Draw items' borders"); - action->setToolTip("Draw borders for blocks on diagram.\nThis reduces performance."); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.draw_graphics_items_borders); - connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.draw_graphics_items_borders = _checked; refreshDiagram(); }); - - action = submenu->addAction("Overlap narrow children"); - action->setToolTip("Children blocks will be overlaped by narrow\nparent blocks. See also \'Blocks narrow size\'.\nThis improves performance."); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.hide_narrow_children); - connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.hide_narrow_children = _checked; refreshDiagram(); }); - - action = submenu->addAction("Hide min-size blocks"); - action->setToolTip("Hides blocks which screen size\nis less than \'Min blocks size\'."); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.hide_minsize_blocks); - connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.hide_minsize_blocks = _checked; refreshDiagram(); }); - - action = submenu->addAction("Build hierarchy only for current thread"); - action->setToolTip("Hierarchy tree will be built\nfor blocks from current thread only.\nThis improves performance\nand saves a lot of memory."); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.only_current_thread_hierarchy); - connect(action, &QAction::triggered, this, &This::onHierarchyFlagChange); - - action = submenu->addAction("Add zero blocks to hierarchy"); - action->setToolTip("Zero duration blocks will be added into hierarchy tree.\nThis reduces performance and increases memory consumption."); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.add_zero_blocks_to_hierarchy); - connect(action, &QAction::triggered, [this](bool _checked) - { - EASY_GLOBALS.add_zero_blocks_to_hierarchy = _checked; - emit EASY_GLOBALS.events.hierarchyFlagChanged(_checked); - }); - - action = submenu->addAction("Enable zero duration blocks on diagram"); - action->setToolTip("If checked then allows diagram to paint zero duration blocks\nwith 1px width on each scale. Otherwise, such blocks will be resized\nto 250ns duration."); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.enable_zero_length); - connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.enable_zero_length = _checked; refreshDiagram(); }); - - action = submenu->addAction("Highlight similar blocks"); - action->setToolTip("Highlight all visible blocks which are similar\nto the current selected block.\nThis reduces performance."); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.highlight_blocks_with_same_id); - connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.highlight_blocks_with_same_id = _checked; refreshDiagram(); }); - - action = submenu->addAction("Collapse blocks on tree reset"); - action->setToolTip("This collapses all blocks on diagram\nafter hierarchy tree reset."); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.collapse_items_on_tree_close); - connect(action, &QAction::triggered, this, &This::onCollapseItemsAfterCloseChanged); - - action = submenu->addAction("Expand all on file open"); - action->setToolTip("If checked then all blocks on diagram\nwill be initially expanded."); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.all_items_expanded_by_default); - connect(action, &QAction::triggered, this, &This::onAllItemsExpandedByDefaultChange); - - action = submenu->addAction("Bind diagram and tree expand"); - action->setToolTip("Expanding/collapsing blocks at diagram expands/collapses\nblocks at hierarchy tree and wise versa."); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.bind_scene_and_tree_expand_status); - connect(action, &QAction::triggered, this, &This::onBindExpandStatusChange); - - action = submenu->addAction("Selecting block changes current thread"); - action->setToolTip("Automatically select thread while selecting a block.\nIf not checked then you will have to select current thread\nmanually double clicking on thread name on a diagram."); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.selecting_block_changes_thread); - connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.selecting_block_changes_thread = _checked; }); - - action = submenu->addAction("Draw event indicators"); - action->setToolTip("Display event indicators under the blocks\n(even if event-blocks are not visible).\nThis slightly reduces performance."); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.enable_event_indicators); - connect(action, &QAction::triggered, this, &This::onEventIndicatorsChange); - - action = submenu->addAction("Use decorated thread names"); - action->setToolTip("Add \'Thread\' word into thread name if there is no one already.\nExamples: \'Render\' will change to \'Render Thread\'\n\'WorkerThread\' will not change."); - action->setCheckable(true); - action->setChecked(EASY_GLOBALS.use_decorated_thread_name); - connect(action, &QAction::triggered, [this](bool _checked) - { - EASY_GLOBALS.use_decorated_thread_name = _checked; - emit EASY_GLOBALS.events.threadNameDecorationChanged(); - }); - - submenu->addSeparator(); - auto actionGroup = new QActionGroup(this); - actionGroup->setExclusive(true); - - action = new QAction("Chrono text at top", actionGroup); - action->setToolTip("Draw duration of selected interval\nat the top of the screen."); - action->setCheckable(true); - action->setData(static_cast(::profiler_gui::ChronoTextPosition_Top)); - if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Top) - action->setChecked(true); - submenu->addAction(action); - connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged); - - action = new QAction("Chrono text at center", actionGroup); - action->setToolTip("Draw duration of selected interval\nat the center of the screen."); - action->setCheckable(true); - action->setData(static_cast(::profiler_gui::ChronoTextPosition_Center)); - if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Center) - action->setChecked(true); - submenu->addAction(action); - connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged); - - action = new QAction("Chrono text at bottom", actionGroup); - action->setToolTip("Draw duration of selected interval\nat the bottom of the screen."); - action->setCheckable(true); - action->setData(static_cast(::profiler_gui::ChronoTextPosition_Bottom)); - if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Bottom) - action->setChecked(true); - submenu->addAction(action); - connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged); - - submenu->addSeparator(); - auto w = new QWidget(submenu); - auto l = new QHBoxLayout(w); - l->setContentsMargins(33, 1, 1, 1); - l->addWidget(new QLabel("Min blocks spacing, px", w), 0, Qt::AlignLeft); - auto spinbox = new QSpinBox(w); - spinbox->setMinimum(0); - spinbox->setValue(EASY_GLOBALS.blocks_spacing); - spinbox->setFixedWidth(50); - connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onSpacingChange(int))); - l->addWidget(spinbox); - w->setLayout(l); - auto waction = new QWidgetAction(submenu); - waction->setDefaultWidget(w); - submenu->addAction(waction); - - w = new QWidget(submenu); - l = new QHBoxLayout(w); - l->setContentsMargins(33, 1, 1, 1); - l->addWidget(new QLabel("Min blocks size, px", w), 0, Qt::AlignLeft); - spinbox = new QSpinBox(w); - spinbox->setMinimum(1); - spinbox->setValue(EASY_GLOBALS.blocks_size_min); - spinbox->setFixedWidth(50); - connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onMinSizeChange(int))); - l->addWidget(spinbox); - w->setLayout(l); - waction = new QWidgetAction(submenu); - waction->setDefaultWidget(w); - submenu->addAction(waction); - - w = new QWidget(submenu); - l = new QHBoxLayout(w); - l->setContentsMargins(33, 1, 1, 1); - l->addWidget(new QLabel("Blocks narrow size, px", w), 0, Qt::AlignLeft); - spinbox = new QSpinBox(w); - spinbox->setMinimum(1); - spinbox->setValue(EASY_GLOBALS.blocks_narrow_size); - spinbox->setFixedWidth(50); - connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onNarrowSizeChange(int))); - l->addWidget(spinbox); - w->setLayout(l); - waction = new QWidgetAction(submenu); - waction->setDefaultWidget(w); - submenu->addAction(waction); - - - submenu = menu->addMenu("Units"); - actionGroup = new QActionGroup(this); - actionGroup->setExclusive(true); - action = new QAction("Auto", actionGroup); - action->setCheckable(true); - action->setData(static_cast(::profiler_gui::TimeUnits_auto)); - if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_auto) - action->setChecked(true); - submenu->addAction(action); - connect(action, &QAction::triggered, this, &This::onUnitsChanged); - - action = new QAction("Milliseconds", actionGroup); - action->setCheckable(true); - action->setData(static_cast(::profiler_gui::TimeUnits_ms)); - if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_ms) - action->setChecked(true); - submenu->addAction(action); - connect(action, &QAction::triggered, this, &This::onUnitsChanged); - - action = new QAction("Microseconds", actionGroup); - action->setCheckable(true); - action->setData(static_cast(::profiler_gui::TimeUnits_us)); - if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_us) - action->setChecked(true); - submenu->addAction(action); - connect(action, &QAction::triggered, this, &This::onUnitsChanged); - - action = new QAction("Nanoseconds", actionGroup); - action->setCheckable(true); - action->setData(static_cast(::profiler_gui::TimeUnits_ns)); - if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_ns) - action->setChecked(true); - submenu->addAction(action); - connect(action, &QAction::triggered, this, &This::onUnitsChanged); - - - submenu = menu->addMenu("Remote"); - m_eventTracingEnableAction = submenu->addAction("Event tracing enabled"); - m_eventTracingEnableAction->setCheckable(true); - m_eventTracingEnableAction->setEnabled(false); - connect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange); - - m_eventTracingPriorityAction = submenu->addAction("Low priority event tracing"); - m_eventTracingPriorityAction->setCheckable(true); - m_eventTracingPriorityAction->setChecked(EASY_OPTION_LOW_PRIORITY_EVENT_TRACING); - m_eventTracingPriorityAction->setEnabled(false); - connect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange); - - - submenu = menu->addMenu("Encoding"); - actionGroup = new QActionGroup(this); - actionGroup->setExclusive(true); - - auto default_codec_mib = QTextCodec::codecForLocale()->mibEnum(); - foreach(int mib, QTextCodec::availableMibs()) - { - auto codec = QTextCodec::codecForMib(mib)->name(); - - action = new QAction(codec, actionGroup); - action->setCheckable(true); - if (mib == default_codec_mib) - action->setChecked(true); - - submenu->addAction(action); - connect(action, &QAction::triggered, this, &This::onEncodingChanged); - } - - auto tb_height = toolbar->height() + 4; - toolbar = addToolBar("FrameToolbar"); - toolbar->setIconSize(::profiler_gui::ICONS_SIZE); - toolbar->setObjectName("ProfilerGUI_FrameToolbar"); - toolbar->setContentsMargins(1, 0, 1, 0); - toolbar->setMinimumHeight(tb_height); - - lbl = new QLabel("Frame time:", toolbar); - lbl->setContentsMargins(5, 2, 2, 2); - toolbar->addWidget(lbl); - - m_frameTimeEdit = new QLineEdit(); - m_frameTimeEdit->setFixedWidth(70); - auto val = new QDoubleValidator(m_frameTimeEdit); - val->setLocale(QLocale::c()); - val->setBottom(0); - m_frameTimeEdit->setValidator(val); - m_frameTimeEdit->setText(QString::number(EASY_GLOBALS.frame_time * 1e-3)); - connect(m_frameTimeEdit, &QLineEdit::editingFinished, this, &This::onFrameTimeEditFinish); - toolbar->addWidget(m_frameTimeEdit); - - lbl = new QLabel("ms", toolbar); - lbl->setContentsMargins(5, 2, 1, 1); - toolbar->addWidget(lbl); - - - connect(graphicsView->view(), &EasyGraphicsView::intervalChanged, treeWidget->tree(), &EasyTreeWidget::setTreeBlocks); - connect(&m_readerTimer, &QTimer::timeout, this, &This::onFileReaderTimeout); - connect(&m_listenerTimer, &QTimer::timeout, this, &This::onListenerTimerTimeout); - - - m_progress = new QProgressDialog("Loading file...", "Cancel", 0, 100, this); - m_progress->setFixedWidth(300); - m_progress->setWindowTitle("EasyProfiler"); - m_progress->setModal(true); - m_progress->setValue(100); - //m_progress->hide(); - connect(m_progress, &QProgressDialog::canceled, this, &This::onFileReaderCancel); - - loadGeometry(); - - if(QCoreApplication::arguments().size() > 1) - { - auto opened_filename = QCoreApplication::arguments().at(1); - loadFile(opened_filename); - } - - connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blockStatusChanged, this, &This::onBlockStatusChange); - connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blocksRefreshRequired, this, &This::onGetBlockDescriptionsClicked); -} - -EasyMainWindow::~EasyMainWindow() -{ - delete m_progress; -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::dragEnterEvent(QDragEnterEvent* drag_event) -{ - if (drag_event->mimeData()->hasUrls()) - drag_event->acceptProposedAction(); -} - -void EasyMainWindow::dragMoveEvent(QDragMoveEvent* drag_event) -{ - if (drag_event->mimeData()->hasUrls()) - drag_event->acceptProposedAction(); -} - -void EasyMainWindow::dragLeaveEvent(QDragLeaveEvent* drag_event) -{ - drag_event->accept(); -} - -void EasyMainWindow::dropEvent(QDropEvent* drop_event) -{ - const auto& urls = drop_event->mimeData()->urls(); - if (!urls.empty()) - loadFile(urls.front().toLocalFile()); -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onOpenFileClicked(bool) -{ - auto action = qobject_cast(sender()); - if (action == nullptr) - return; - - if (action == m_loadActionMenu->menuAction()) - { - auto filename = QFileDialog::getOpenFileName(this, "Open profiler log", m_lastFiles.empty() ? QString() : m_lastFiles.front(), "Profiler Log File (*.prof);;All Files (*.*)"); - if (!filename.isEmpty()) - loadFile(filename); - } - else - { - loadFile(action->text()); - } -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::addFileToList(const QString& filename) -{ - m_lastFiles.push_front(filename); - - auto action = new QAction(filename, this); - connect(action, &QAction::triggered, this, &This::onOpenFileClicked); - auto fileActions = m_loadActionMenu->actions(); - if (fileActions.empty()) - m_loadActionMenu->addAction(action); - else - m_loadActionMenu->insertAction(fileActions.front(), action); - - if (m_lastFiles.size() > 10) - { - // Keep 10 files at the list - m_lastFiles.pop_back(); - m_loadActionMenu->removeAction(fileActions.back()); - delete fileActions.back(); - } -} - -void EasyMainWindow::loadFile(const QString& filename) -{ - const auto i = filename.lastIndexOf(QChar('/')); - const auto j = filename.lastIndexOf(QChar('\\')); - m_progress->setLabelText(QString("Loading %1...").arg(filename.mid(::std::max(i, j) + 1))); - - m_progress->setValue(0); - m_progress->show(); - m_readerTimer.start(LOADER_TIMER_INTERVAL); - m_reader.load(filename); -} - -void EasyMainWindow::readStream(::std::stringstream& data) -{ - m_progress->setLabelText(tr("Reading from stream...")); - - m_progress->setValue(0); - m_progress->show(); - m_readerTimer.start(LOADER_TIMER_INTERVAL); - m_reader.load(data); -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onSaveFileClicked(bool) -{ - if (m_serializedBlocks.empty()) - return; - - QString lastFile = m_lastFiles.empty() ? QString() : m_lastFiles.front(); - - const auto i = lastFile.lastIndexOf(QChar('/')); - const auto j = lastFile.lastIndexOf(QChar('\\')); - auto k = ::std::max(i, j); - - QString dir; - if (k > 0) - dir = lastFile.mid(0, ++k); - - auto filename = QFileDialog::getSaveFileName(this, "Save profiler log", dir, "Profiler Log File (*.prof);;All Files (*.*)"); - if (!filename.isEmpty()) - { - bool inOk = false, outOk = false; - int8_t retry1 = -1; - while (++retry1 < 4) - { - ::std::ifstream inFile(m_bNetworkFileRegime ? NETWORK_CACHE_FILE : lastFile.toStdString().c_str(), ::std::fstream::binary); - if (!inFile.is_open()) - { - ::std::this_thread::sleep_for(::std::chrono::milliseconds(500)); - continue; - } - - inOk = true; - - int8_t retry2 = -1; - while (++retry2 < 4) - { - ::std::ofstream outFile(filename.toStdString(), ::std::fstream::binary); - if (!outFile.is_open()) - { - ::std::this_thread::sleep_for(::std::chrono::milliseconds(500)); - continue; - } - - outFile << inFile.rdbuf(); - outOk = true; - break; - } - - break; - } - - if (outOk) - { - if (m_bNetworkFileRegime) - QFile::remove(QString(NETWORK_CACHE_FILE)); - addFileToList(filename); - m_bNetworkFileRegime = false; - } - else if (inOk) - { - QMessageBox::warning(this, "Warning", "Can not open destination file.\nSaving incomplete.", QMessageBox::Close); - } - else - { - if (m_bNetworkFileRegime) - QMessageBox::warning(this, "Warning", "Can not open network cache file.\nSaving incomplete.", QMessageBox::Close); - else - QMessageBox::warning(this, "Warning", "Can not open source file.\nSaving incomplete.", QMessageBox::Close); - } - } -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::clear() -{ - static_cast(m_treeWidget->widget())->clear(true); - static_cast(m_graphicsView->widget())->clear(); - -#if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 - static_cast(m_descTreeWidget->widget())->clear(); -#endif - if (m_dialogDescTree != nullptr) - m_dialogDescTree->clear(); - - EASY_GLOBALS.selected_thread = 0; - ::profiler_gui::set_max(EASY_GLOBALS.selected_block); - ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id); - EASY_GLOBALS.profiler_blocks.clear(); - EASY_GLOBALS.descriptors.clear(); - EASY_GLOBALS.gui_blocks.clear(); - - m_serializedBlocks.clear(); - m_serializedDescriptors.clear(); - - m_saveAction->setEnabled(false); - m_deleteAction->setEnabled(false); - - m_bNetworkFileRegime = false; -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::refreshDiagram() -{ - static_cast(m_graphicsView->widget())->view()->scene()->update(); -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onDeleteClicked(bool) -{ - auto button = QMessageBox::question(this, "Clear all profiled data", "All profiled data is going to be deleted!\nContinue?", QMessageBox::Yes, QMessageBox::No); - if (button == QMessageBox::Yes) - clear(); -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onExitClicked(bool) -{ - close(); -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onEncodingChanged(bool) -{ - auto _sender = qobject_cast(sender()); - auto name = _sender->text(); - QTextCodec *codec = QTextCodec::codecForName(name.toStdString().c_str()); - QTextCodec::setCodecForLocale(codec); -} - -void EasyMainWindow::onChronoTextPosChanged(bool) -{ - auto _sender = qobject_cast(sender()); - EASY_GLOBALS.chrono_text_position = static_cast<::profiler_gui::ChronometerTextPosition>(_sender->data().toInt()); - refreshDiagram(); -} - -void EasyMainWindow::onUnitsChanged(bool) -{ - auto _sender = qobject_cast(sender()); - EASY_GLOBALS.time_units = static_cast<::profiler_gui::TimeUnits>(_sender->data().toInt()); -} - -void EasyMainWindow::onEventIndicatorsChange(bool _checked) -{ - EASY_GLOBALS.enable_event_indicators = _checked; - refreshDiagram(); -} - -void EasyMainWindow::onEnableDisableStatistics(bool _checked) -{ - EASY_GLOBALS.enable_statistics = _checked; - - auto action = qobject_cast(sender()); - if (action != nullptr) - { - auto f = action->font(); - f.setBold(_checked); - action->setFont(f); - - if (_checked) - { - action->setText("Statistics enabled"); - SET_ICON(action, ":/Stats"); - } - else - { - action->setText("Statistics disabled"); - SET_ICON(action, ":/Stats-off"); - } - } -} - -void EasyMainWindow::onCollapseItemsAfterCloseChanged(bool _checked) -{ - EASY_GLOBALS.collapse_items_on_tree_close = _checked; -} - -void EasyMainWindow::onAllItemsExpandedByDefaultChange(bool _checked) -{ - EASY_GLOBALS.all_items_expanded_by_default = _checked; -} - -void EasyMainWindow::onBindExpandStatusChange(bool _checked) -{ - EASY_GLOBALS.bind_scene_and_tree_expand_status = _checked; -} - -void EasyMainWindow::onHierarchyFlagChange(bool _checked) -{ - EASY_GLOBALS.only_current_thread_hierarchy = _checked; - emit EASY_GLOBALS.events.hierarchyFlagChanged(_checked); -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onExpandAllClicked(bool) -{ - for (auto& block : EASY_GLOBALS.gui_blocks) - block.expanded = true; - - emit EASY_GLOBALS.events.itemsExpandStateChanged(); - - auto tree = static_cast(m_treeWidget->widget())->tree(); - const QSignalBlocker b(tree); - tree->expandAll(); -} - -void EasyMainWindow::onCollapseAllClicked(bool) -{ - for (auto& block : EASY_GLOBALS.gui_blocks) - block.expanded = false; - - emit EASY_GLOBALS.events.itemsExpandStateChanged(); - - auto tree = static_cast(m_treeWidget->widget())->tree(); - const QSignalBlocker b(tree); - tree->collapseAll(); -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onSpacingChange(int _value) -{ - EASY_GLOBALS.blocks_spacing = _value; - refreshDiagram(); -} - -void EasyMainWindow::onMinSizeChange(int _value) -{ - EASY_GLOBALS.blocks_size_min = _value; - refreshDiagram(); -} - -void EasyMainWindow::onNarrowSizeChange(int _value) -{ - EASY_GLOBALS.blocks_narrow_size = _value; - refreshDiagram(); -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onEditBlocksClicked(bool) -{ - if (m_descTreeDialog != nullptr) - { - m_descTreeDialog->raise(); - return; - } - - m_descTreeDialog = new QDialog(); - m_descTreeDialog->setAttribute(Qt::WA_DeleteOnClose, true); - m_descTreeDialog->setWindowTitle("EasyProfiler"); - m_descTreeDialog->resize(800, 600); - connect(m_descTreeDialog, &QDialog::finished, this, &This::onDescTreeDialogClose); - - auto l = new QVBoxLayout(m_descTreeDialog); - m_dialogDescTree = new EasyDescWidget(m_descTreeDialog); - l->addWidget(m_dialogDescTree); - m_descTreeDialog->setLayout(l); - - m_dialogDescTree->build(); - m_descTreeDialog->show(); -} - -void EasyMainWindow::onDescTreeDialogClose(int) -{ - disconnect(m_descTreeDialog, &QDialog::finished, this, &This::onDescTreeDialogClose); - m_dialogDescTree = nullptr; - m_descTreeDialog = nullptr; -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::closeEvent(QCloseEvent* close_event) -{ - saveSettingsAndGeometry(); - - if (m_descTreeDialog != nullptr) - { - m_descTreeDialog->reject(); - m_descTreeDialog = nullptr; - m_dialogDescTree = nullptr; - } - - Parent::closeEvent(close_event); -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::loadSettings() -{ - QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME); - settings.beginGroup("main"); - - auto last_files = settings.value("last_files"); - if (!last_files.isNull()) - m_lastFiles = last_files.toStringList(); - - auto last_addr = settings.value("ip_address"); - if (!last_addr.isNull()) - m_lastAddress = last_addr.toString(); - - auto last_port = settings.value("port"); - if (!last_port.isNull()) - m_lastPort = (uint16_t)last_port.toUInt(); - - - auto val = settings.value("chrono_text_position"); - if (!val.isNull()) - EASY_GLOBALS.chrono_text_position = static_cast<::profiler_gui::ChronometerTextPosition>(val.toInt()); - - val = settings.value("time_units"); - if (!val.isNull()) - EASY_GLOBALS.time_units = static_cast<::profiler_gui::TimeUnits>(val.toInt()); - - - val = settings.value("frame_time"); - if (!val.isNull()) - EASY_GLOBALS.frame_time = val.toFloat(); - - val = settings.value("blocks_spacing"); - if (!val.isNull()) - EASY_GLOBALS.blocks_spacing = val.toInt(); - - val = settings.value("blocks_size_min"); - if (!val.isNull()) - EASY_GLOBALS.blocks_size_min = val.toInt(); - - val = settings.value("blocks_narrow_size"); - if (!val.isNull()) - EASY_GLOBALS.blocks_narrow_size = val.toInt(); - - - auto flag = settings.value("draw_graphics_items_borders"); - if (!flag.isNull()) - EASY_GLOBALS.draw_graphics_items_borders = flag.toBool(); - - flag = settings.value("hide_narrow_children"); - if (!flag.isNull()) - EASY_GLOBALS.hide_narrow_children = flag.toBool(); - - flag = settings.value("hide_minsize_blocks"); - if (!flag.isNull()) - EASY_GLOBALS.hide_minsize_blocks = flag.toBool(); - - flag = settings.value("collapse_items_on_tree_close"); - if (!flag.isNull()) - EASY_GLOBALS.collapse_items_on_tree_close = flag.toBool(); - - flag = settings.value("all_items_expanded_by_default"); - if (!flag.isNull()) - EASY_GLOBALS.all_items_expanded_by_default = flag.toBool(); - - flag = settings.value("only_current_thread_hierarchy"); - if (!flag.isNull()) - EASY_GLOBALS.only_current_thread_hierarchy = flag.toBool(); - - flag = settings.value("enable_zero_length"); - if (!flag.isNull()) - EASY_GLOBALS.enable_zero_length = flag.toBool(); - - flag = settings.value("add_zero_blocks_to_hierarchy"); - if (!flag.isNull()) - EASY_GLOBALS.add_zero_blocks_to_hierarchy = flag.toBool(); - - - flag = settings.value("highlight_blocks_with_same_id"); - if (!flag.isNull()) - EASY_GLOBALS.highlight_blocks_with_same_id = flag.toBool(); - - flag = settings.value("bind_scene_and_tree_expand_status"); - if (!flag.isNull()) - EASY_GLOBALS.bind_scene_and_tree_expand_status = flag.toBool(); - - flag = settings.value("selecting_block_changes_thread"); - if (!flag.isNull()) - EASY_GLOBALS.selecting_block_changes_thread = flag.toBool(); - - flag = settings.value("enable_event_indicators"); - if (!flag.isNull()) - EASY_GLOBALS.enable_event_indicators = flag.toBool(); - - flag = settings.value("use_decorated_thread_name"); - if (!flag.isNull()) - EASY_GLOBALS.use_decorated_thread_name = flag.toBool(); - - flag = settings.value("enable_statistics"); - if (!flag.isNull()) - EASY_GLOBALS.enable_statistics = flag.toBool(); - - QString encoding = settings.value("encoding", "UTF-8").toString(); - auto default_codec_mib = QTextCodec::codecForName(encoding.toStdString().c_str())->mibEnum(); - auto default_codec = QTextCodec::codecForMib(default_codec_mib); - QTextCodec::setCodecForLocale(default_codec); - - settings.endGroup(); -} - -void EasyMainWindow::loadGeometry() -{ - QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME); - settings.beginGroup("main"); - - auto geometry = settings.value("geometry").toByteArray(); - if (!geometry.isEmpty()) - restoreGeometry(geometry); - - auto state = settings.value("windowState").toByteArray(); - if (!state.isEmpty()) - restoreState(state); - - settings.endGroup(); -} - -void EasyMainWindow::saveSettingsAndGeometry() -{ - QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME); - settings.beginGroup("main"); - - settings.setValue("geometry", this->saveGeometry()); - settings.setValue("windowState", this->saveState()); - settings.setValue("last_files", m_lastFiles); - settings.setValue("ip_address", m_lastAddress); - settings.setValue("port", (quint32)m_lastPort); - settings.setValue("chrono_text_position", static_cast(EASY_GLOBALS.chrono_text_position)); - settings.setValue("time_units", static_cast(EASY_GLOBALS.time_units)); - settings.setValue("frame_time", EASY_GLOBALS.frame_time); - settings.setValue("blocks_spacing", EASY_GLOBALS.blocks_spacing); - settings.setValue("blocks_size_min", EASY_GLOBALS.blocks_size_min); - settings.setValue("blocks_narrow_size", EASY_GLOBALS.blocks_narrow_size); - settings.setValue("draw_graphics_items_borders", EASY_GLOBALS.draw_graphics_items_borders); - settings.setValue("hide_narrow_children", EASY_GLOBALS.hide_narrow_children); - settings.setValue("hide_minsize_blocks", EASY_GLOBALS.hide_minsize_blocks); - settings.setValue("collapse_items_on_tree_close", EASY_GLOBALS.collapse_items_on_tree_close); - settings.setValue("all_items_expanded_by_default", EASY_GLOBALS.all_items_expanded_by_default); - settings.setValue("only_current_thread_hierarchy", EASY_GLOBALS.only_current_thread_hierarchy); - settings.setValue("enable_zero_length", EASY_GLOBALS.enable_zero_length); - settings.setValue("add_zero_blocks_to_hierarchy", EASY_GLOBALS.add_zero_blocks_to_hierarchy); - settings.setValue("highlight_blocks_with_same_id", EASY_GLOBALS.highlight_blocks_with_same_id); - settings.setValue("bind_scene_and_tree_expand_status", EASY_GLOBALS.bind_scene_and_tree_expand_status); - settings.setValue("selecting_block_changes_thread", EASY_GLOBALS.selecting_block_changes_thread); - settings.setValue("enable_event_indicators", EASY_GLOBALS.enable_event_indicators); - settings.setValue("use_decorated_thread_name", EASY_GLOBALS.use_decorated_thread_name); - settings.setValue("enable_statistics", EASY_GLOBALS.enable_statistics); - settings.setValue("encoding", QTextCodec::codecForLocale()->name()); - - settings.endGroup(); -} - -void EasyMainWindow::setDisconnected(bool _showMessage) -{ - if (_showMessage) - QMessageBox::warning(this, "Warning", "Application was disconnected", QMessageBox::Close); - - EASY_GLOBALS.connected = false; - m_captureAction->setEnabled(false); - SET_ICON(m_connectAction, ":/Connection"); - - m_eventTracingEnableAction->setEnabled(false); - m_eventTracingPriorityAction->setEnabled(false); - - emit EASY_GLOBALS.events.connectionChanged(false); - -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onListenerTimerTimeout() -{ - if (!m_listener.connected()) - m_listenerDialog->reject(); -} - -void EasyMainWindow::onListenerDialogClose(int) -{ - m_listenerTimer.stop(); - disconnect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose); - m_listenerDialog = nullptr; - - switch (m_listener.regime()) - { - case LISTENER_CAPTURE: - { - m_listenerDialog = new QMessageBox(QMessageBox::Information, "Receiving data...", "This process may take some time.", QMessageBox::NoButton, this); - m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true); - m_listenerDialog->show(); - - m_listener.stopCapture(); - - m_listenerDialog->reject(); - m_listenerDialog = nullptr; - - if (m_listener.size() != 0) - { - readStream(m_listener.data()); - m_listener.clearData(); - } - - break; - } - - case LISTENER_DESCRIBE: - { - break; - } - - default: - return; - } - - if (!m_listener.connected()) - { - setDisconnected(); - } -} - - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onFileReaderTimeout() -{ - if (m_reader.done()) - { - auto nblocks = m_reader.size(); - if (nblocks != 0) - { - static_cast(m_treeWidget->widget())->clear(true); - - ::profiler::SerializedData serialized_blocks; - ::profiler::SerializedData serialized_descriptors; - ::profiler::descriptors_list_t descriptors; - ::profiler::blocks_t blocks; - ::profiler::thread_blocks_tree_t threads_map; - QString filename; - uint32_t descriptorsNumberInFile = 0; - m_reader.get(serialized_blocks, serialized_descriptors, descriptors, blocks, threads_map, descriptorsNumberInFile, filename); - - if (threads_map.size() > 0xff) - { - if (m_reader.isFile()) - qWarning() << "Warning: file " << filename << " contains " << threads_map.size() << " threads!"; - else - qWarning() << "Warning: input stream contains " << threads_map.size() << " threads!"; - qWarning() << "Warning: Currently, maximum number of displayed threads is 255! Some threads will not be displayed."; - } - - m_bNetworkFileRegime = !m_reader.isFile(); - if (!m_bNetworkFileRegime) - { - auto index = m_lastFiles.indexOf(filename, 0); - if (index == -1) - { - // This file is totally new. Add it to the list. - addFileToList(filename); - } - else if (index != 0) - { - // This file has been already loaded. Move it to the front. - m_lastFiles.move(index, 0); - auto fileActions = m_loadActionMenu->actions(); - auto action = fileActions.at(index); - m_loadActionMenu->removeAction(action); - m_loadActionMenu->insertAction(fileActions.front(), action); - } - } - m_serializedBlocks = ::std::move(serialized_blocks); - m_serializedDescriptors = ::std::move(serialized_descriptors); - m_descriptorsNumberInFile = descriptorsNumberInFile; - EASY_GLOBALS.selected_thread = 0; - ::profiler_gui::set_max(EASY_GLOBALS.selected_block); - ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id); - EASY_GLOBALS.profiler_blocks.swap(threads_map); - EASY_GLOBALS.descriptors.swap(descriptors); - - EASY_GLOBALS.gui_blocks.clear(); - EASY_GLOBALS.gui_blocks.resize(nblocks); - memset(EASY_GLOBALS.gui_blocks.data(), 0, sizeof(::profiler_gui::EasyBlock) * nblocks); - for (decltype(nblocks) i = 0; i < nblocks; ++i) { - auto& guiblock = EASY_GLOBALS.gui_blocks[i]; - guiblock.tree = ::std::move(blocks[i]); -#ifdef EASY_TREE_WIDGET__USE_VECTOR - ::profiler_gui::set_max(guiblock.tree_item); -#endif - } - - static_cast(m_graphicsView->widget())->view()->setTree(EASY_GLOBALS.profiler_blocks); - -#if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 - static_cast(m_descTreeWidget->widget())->build(); -#endif - if (m_dialogDescTree != nullptr) - m_dialogDescTree->build(); - - m_saveAction->setEnabled(true); - m_deleteAction->setEnabled(true); - } - else - { - QMessageBox::warning(this, "Warning", QString("Can not read profiled blocks.\n\nReason:\n%1").arg(m_reader.getError()), QMessageBox::Close); - - if (m_reader.isFile()) - { - auto index = m_lastFiles.indexOf(m_reader.filename(), 0); - if (index >= 0) - { - // Remove unexisting file from list - m_lastFiles.removeAt(index); - auto action = m_loadActionMenu->actions().at(index); - m_loadActionMenu->removeAction(action); - delete action; - } - } - } - - m_reader.interrupt(); - - m_readerTimer.stop(); - m_progress->setValue(100); - //m_progress->hide(); - - if (EASY_GLOBALS.all_items_expanded_by_default) - { - onExpandAllClicked(true); - } - } - else - { - m_progress->setValue(m_reader.progress()); - } -} - -void EasyMainWindow::onFileReaderCancel() -{ - m_readerTimer.stop(); - m_reader.interrupt(); - m_progress->setValue(100); - //m_progress->hide(); -} - -////////////////////////////////////////////////////////////////////////// - -EasyFileReader::EasyFileReader() -{ - -} - -EasyFileReader::~EasyFileReader() -{ - interrupt(); -} - -const bool EasyFileReader::isFile() const -{ - return m_isFile; -} - -bool EasyFileReader::done() const -{ - return m_bDone.load(::std::memory_order_acquire); -} - -int EasyFileReader::progress() const -{ - return m_progress.load(::std::memory_order_acquire); -} - -unsigned int EasyFileReader::size() const -{ - return m_size.load(::std::memory_order_acquire); -} - -const QString& EasyFileReader::filename() const -{ - return m_filename; -} - -void EasyFileReader::load(const QString& _filename) -{ - interrupt(); - - m_isFile = true; - m_filename = _filename; - m_thread = ::std::thread([this](bool _enableStatistics) { - m_size.store(fillTreesFromFile(m_progress, m_filename.toStdString().c_str(), m_serializedBlocks, m_serializedDescriptors, - m_descriptors, m_blocks, m_blocksTree, m_descriptorsNumberInFile, _enableStatistics, m_errorMessage), ::std::memory_order_release); - m_progress.store(100, ::std::memory_order_release); - m_bDone.store(true, ::std::memory_order_release); - }, EASY_GLOBALS.enable_statistics); -} - -void EasyFileReader::load(::std::stringstream& _stream) -{ - interrupt(); - - m_isFile = false; - m_filename.clear(); - -#if defined(__GNUC__) && __GNUC__ < 5 && !defined(__llvm__) - // gcc 4 has a known bug which has been solved in gcc 5: - // std::stringstream has no swap() method :( - // have to copy all contents... Use gcc 5 or higher! -#pragma message "Warning: in gcc 4 and lower std::stringstream has no swap()! Memory consumption may increase! Better use gcc 5 or higher instead." - m_stream.str(_stream.str()); -#else - m_stream.swap(_stream); -#endif - - m_thread = ::std::thread([this](bool _enableStatistics) { - ::std::ofstream cache_file(NETWORK_CACHE_FILE, ::std::fstream::binary); - if (cache_file.is_open()) { - cache_file << m_stream.str(); - cache_file.close(); - } - m_size.store(fillTreesFromStream(m_progress, m_stream, m_serializedBlocks, m_serializedDescriptors, m_descriptors, - m_blocks, m_blocksTree, m_descriptorsNumberInFile, _enableStatistics, m_errorMessage), ::std::memory_order_release); - m_progress.store(100, ::std::memory_order_release); - m_bDone.store(true, ::std::memory_order_release); - }, EASY_GLOBALS.enable_statistics); -} - -void EasyFileReader::interrupt() -{ - m_progress.store(-100, ::std::memory_order_release); - if (m_thread.joinable()) - m_thread.join(); - - m_bDone.store(false, ::std::memory_order_release); - m_progress.store(0, ::std::memory_order_release); - m_size.store(0, ::std::memory_order_release); - m_serializedBlocks.clear(); - m_serializedDescriptors.clear(); - m_descriptors.clear(); - m_blocks.clear(); - m_blocksTree.clear(); - m_descriptorsNumberInFile = 0; - - clear_stream(m_stream); - clear_stream(m_errorMessage); -} - -void EasyFileReader::get(::profiler::SerializedData& _serializedBlocks, ::profiler::SerializedData& _serializedDescriptors, - ::profiler::descriptors_list_t& _descriptors, ::profiler::blocks_t& _blocks, - ::profiler::thread_blocks_tree_t& _tree, uint32_t& _descriptorsNumberInFile, QString& _filename) -{ - if (done()) - { - m_serializedBlocks.swap(_serializedBlocks); - m_serializedDescriptors.swap(_serializedDescriptors); - ::profiler::descriptors_list_t(::std::move(m_descriptors)).swap(_descriptors); - m_blocks.swap(_blocks); - m_blocksTree.swap(_tree); - m_filename.swap(_filename); - _descriptorsNumberInFile = m_descriptorsNumberInFile; - } -} - -QString EasyFileReader::getError() -{ - return QString(m_errorMessage.str().c_str()); -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onEventTracingPriorityChange(bool _checked) -{ - if (EASY_GLOBALS.connected) - m_listener.send(profiler::net::BoolMessage(profiler::net::MESSAGE_TYPE_EVENT_TRACING_PRIORITY, _checked)); -} - -void EasyMainWindow::onEventTracingEnableChange(bool _checked) -{ - if (EASY_GLOBALS.connected) - m_listener.send(profiler::net::BoolMessage(profiler::net::MESSAGE_TYPE_EVENT_TRACING_STATUS, _checked)); -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onFrameTimeEditFinish() -{ - auto text = m_frameTimeEdit->text(); - if (text.contains(QChar(','))) - { - text.remove(QChar('.')).replace(QChar(','), QChar('.')); - m_frameTimeEdit->setText(text); - } - - EASY_GLOBALS.frame_time = text.toFloat() * 1e3f; - emit EASY_GLOBALS.events.timelineMarkerChanged(); -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onConnectClicked(bool) -{ - auto text = m_addressEdit->text(); -// auto parts = text.split(QChar('.')); -// if (parts.size() != 4) -// { -// QMessageBox::warning(this, "Warning", "Invalid IP-Address", QMessageBox::Close); -// -// if (EASY_GLOBALS.connected) -// { -// // Restore last values -// m_addressEdit->setText(m_lastAddress); -// m_portEdit->setText(QString::number(m_lastPort)); -// } -// -// return; -// } -// -// for (auto& part : parts) -// { -// int i = 0; -// for (; i < part.size(); ++i) -// { -// if (part[i] != QChar('0')) -// break; -// } -// -// if (i < part.size()) -// part = part.mid(i); -// else -// part = "0"; -// } - - QString& address = text;// parts.join(QChar('.')); - const decltype(m_lastPort) port = m_portEdit->text().toUShort(); - //m_addressEdit->setText(address); - - const bool isReconnecting = (EASY_GLOBALS.connected && m_listener.port() == port && address.toStdString() == m_listener.address()); - if (EASY_GLOBALS.connected) - { - if (QMessageBox::question(this, isReconnecting ? "Reconnect" : "New connection", QString("Current connection will be broken\n\n%1") - .arg(isReconnecting ? "Re-connect?" : "Establish new connection?"), - QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes) - { - if (!isReconnecting) - { - // Restore last values - m_addressEdit->setText(m_lastAddress); - m_portEdit->setText(QString::number(m_lastPort)); - } - - return; - } - } - - profiler::net::EasyProfilerStatus reply(false, false, false); - if (!m_listener.connect(address.toStdString().c_str(), port, reply)) - { - if (EASY_GLOBALS.connected && !isReconnecting) - { - if (QMessageBox::warning(this, "Warning", QString("Cannot connect to %1\n\nRestore previous connection?").arg(address), - QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) - { - if (!m_listener.connect(m_lastAddress.toStdString().c_str(), m_lastPort, reply)) - { - QMessageBox::warning(this, "Warning", "Cannot restore previous connection", QMessageBox::Close); - setDisconnected(false); - m_lastAddress = ::std::move(address); - m_lastPort = port; - } - else - { - m_addressEdit->setText(m_lastAddress); - m_portEdit->setText(QString::number(m_lastPort)); - // QMessageBox::information(this, "Information", "Previous connection restored", QMessageBox::Close); - } - } - else - { - setDisconnected(false); - m_lastAddress = ::std::move(address); - m_lastPort = port; - } - } - else - { - QMessageBox::warning(this, "Warning", QString("Cannot connect to %1").arg(address), QMessageBox::Close); - if (EASY_GLOBALS.connected) - setDisconnected(false); - - if (!isReconnecting) - { - m_lastAddress = ::std::move(address); - m_lastPort = port; - } - } - - return; - } - - m_lastAddress = ::std::move(address); - m_lastPort = port; - - qInfo() << "Connected successfully"; - EASY_GLOBALS.connected = true; - m_captureAction->setEnabled(true); - SET_ICON(m_connectAction, ":/Connection-on"); - - disconnect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange); - disconnect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange); - - m_eventTracingEnableAction->setEnabled(true); - m_eventTracingPriorityAction->setEnabled(true); - - m_eventTracingEnableAction->setChecked(reply.isEventTracingEnabled); - m_eventTracingPriorityAction->setChecked(reply.isLowPriorityEventTracing); - - connect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange); - connect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange); - - emit EASY_GLOBALS.events.connectionChanged(true); -} - -void EasyMainWindow::onCaptureClicked(bool) -{ - if (!EASY_GLOBALS.connected) - { - QMessageBox::warning(this, "Warning", "No connection with profiling app", QMessageBox::Close); - return; - } - - if (m_listener.regime() != LISTENER_IDLE) - { - if (m_listener.regime() == LISTENER_CAPTURE) - QMessageBox::warning(this, "Warning", "Already capturing frames.\nFinish old capturing session first.", QMessageBox::Close); - else - QMessageBox::warning(this, "Warning", "Capturing blocks description.\nFinish old capturing session first.", QMessageBox::Close); - return; - } - - if (!m_listener.startCapture()) - { - // Connection lost. Try to restore connection. - - profiler::net::EasyProfilerStatus reply(false, false, false); - if (!m_listener.connect(m_lastAddress.toStdString().c_str(), m_lastPort, reply)) - { - setDisconnected(); - return; - } - - if (!m_listener.startCapture()) - { - setDisconnected(); - return; - } - } - - m_listenerTimer.start(250); - - m_listenerDialog = new QMessageBox(QMessageBox::Information, "Capturing frames...", "Close this dialog to stop capturing.", QMessageBox::NoButton, this); - - auto button = new QToolButton(m_listenerDialog); - button->setAutoRaise(true); - button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - button->setIconSize(::profiler_gui::ICONS_SIZE); - button->setIcon(QIcon(":/Stop")); - button->setText("Stop"); - m_listenerDialog->addButton(button, QMessageBox::AcceptRole); - - m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true); - connect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose); - m_listenerDialog->show(); -} - -void EasyMainWindow::onGetBlockDescriptionsClicked(bool) -{ - if (!EASY_GLOBALS.connected) - { - QMessageBox::warning(this, "Warning", "No connection with profiling app", QMessageBox::Close); - return; - } - - if (m_listener.regime() != LISTENER_IDLE) - { - if (m_listener.regime() == LISTENER_DESCRIBE) - QMessageBox::warning(this, "Warning", "Already capturing blocks description.\nFinish old capturing session first.", QMessageBox::Close); - else - QMessageBox::warning(this, "Warning", "Capturing capturing frames.\nFinish old capturing session first.", QMessageBox::Close); - return; - } - - m_listenerDialog = new QMessageBox(QMessageBox::Information, "Waiting for blocks...", "This may take some time.", QMessageBox::NoButton, this); - m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true); - m_listenerDialog->show(); - - m_listener.requestBlocksDescription(); - - m_listenerDialog->reject(); - m_listenerDialog = nullptr; - - if (m_listener.size() != 0) - { - // Read descriptions from stream - - decltype(EASY_GLOBALS.descriptors) descriptors; - decltype(m_serializedDescriptors) serializedDescriptors; - ::std::stringstream errorMessage; - if (readDescriptionsFromStream(m_listener.data(), serializedDescriptors, descriptors, errorMessage)) - { - // Merge old and new descriptions - - bool cancel = false; - const bool doFlush = m_descriptorsNumberInFile > descriptors.size(); - if (doFlush && !m_serializedBlocks.empty()) - { - auto button = QMessageBox::question(this, "Information", - QString("New blocks description number = %1\nis less than the old one = %2.\nTo avoid possible conflicts\nall profiled data will be deleted.\nContinue?") - .arg(descriptors.size()) - .arg(m_descriptorsNumberInFile), - QMessageBox::Yes, QMessageBox::No); - - if (button == QMessageBox::Yes) - clear(); // Clear all contents because new descriptors list conflicts with old one - else - cancel = true; - } - - if (!cancel) - { - if (!doFlush && m_descriptorsNumberInFile < EASY_GLOBALS.descriptors.size()) - { - // There are dynamically added descriptors, add them to the new list too - - auto newnumber = static_cast(descriptors.size()); - auto size = static_cast(EASY_GLOBALS.descriptors.size()); - auto diff = newnumber - size; - decltype(newnumber) failnumber = 0; - - descriptors.reserve(descriptors.size() + EASY_GLOBALS.descriptors.size() - m_descriptorsNumberInFile); - for (auto i = m_descriptorsNumberInFile; i < size; ++i) - { - auto id = EASY_GLOBALS.descriptors[i]->id(); - if (id < newnumber) - descriptors.push_back(descriptors[id]); - else - ++failnumber; - } - - if (failnumber != 0) - { - // There are some errors... - - // revert changes - descriptors.resize(newnumber); - - // clear all profiled data to avoid conflicts - auto button = QMessageBox::question(this, "Information", - "There are errors while merging block descriptions lists.\nTo avoid possible conflicts\nall profiled data will be deleted.\nContinue?", - QMessageBox::Yes, QMessageBox::No); - - if (button == QMessageBox::Yes) - clear(); // Clear all contents because new descriptors list conflicts with old one - else - cancel = true; - } - - if (!cancel && diff != 0) - { - for (auto& b : EASY_GLOBALS.gui_blocks) - { - if (b.tree.node->id() >= m_descriptorsNumberInFile) - b.tree.node->setId(b.tree.node->id() + diff); - } - - m_descriptorsNumberInFile = newnumber; - } - } - - if (!cancel) - { - EASY_GLOBALS.descriptors.swap(descriptors); - m_serializedDescriptors.swap(serializedDescriptors); - m_descriptorsNumberInFile = static_cast(EASY_GLOBALS.descriptors.size()); - - if (m_descTreeDialog != nullptr) - { -#if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 - static_cast(m_descTreeWidget->widget())->build(); -#endif - m_dialogDescTree->build(); - m_descTreeDialog->raise(); - } - else - { - onEditBlocksClicked(true); - } - } - } - } - else - { - QMessageBox::warning(this, "Warning", QString("Can not read blocks description from stream.\n\nReason:\n%1").arg(errorMessage.str().c_str()), QMessageBox::Close); - } - - m_listener.clearData(); - } - - if (!m_listener.connected()) - { - setDisconnected(); - } -} - -////////////////////////////////////////////////////////////////////////// - -void EasyMainWindow::onBlockStatusChange(::profiler::block_id_t _id, ::profiler::EasyBlockStatus _status) -{ - if (EASY_GLOBALS.connected) - m_listener.send(profiler::net::BlockStatusMessage(_id, static_cast(_status))); -} - -////////////////////////////////////////////////////////////////////////// - -EasySocketListener::EasySocketListener() : m_receivedSize(0), m_port(0), m_regime(LISTENER_IDLE) -{ - m_bInterrupt = ATOMIC_VAR_INIT(false); - m_bConnected = ATOMIC_VAR_INIT(false); - m_bStopReceive = ATOMIC_VAR_INIT(false); -} - -EasySocketListener::~EasySocketListener() -{ - m_bInterrupt.store(true, ::std::memory_order_release); - if (m_thread.joinable()) - m_thread.join(); -} - -bool EasySocketListener::connected() const -{ - return m_bConnected.load(::std::memory_order_acquire); -} - -EasyListenerRegime EasySocketListener::regime() const -{ - return m_regime; -} - -uint64_t EasySocketListener::size() const -{ - return m_receivedSize; -} - -::std::stringstream& EasySocketListener::data() -{ - return m_receivedData; -} - +/************************************************************************ +* file name : main_window.cpp +* ----------------- : +* creation time : 2016/06/26 +* author : Victor Zarubkin +* email : v.s.zarubkin@gmail.com +* ----------------- : +* description : The file contains implementation of MainWindow for easy_profiler GUI. +* ----------------- : +* change log : * 2016/06/26 Victor Zarubkin: Initial commit. +* : +* : * 2016/06/27 Victor Zarubkin: Passing blocks number to EasyTreeWidget::setTree(). +* : +* : * 2016/06/29 Victor Zarubkin: Added menu with tests. +* : +* : * 2016/06/30 Sergey Yagovtsev: Open file by command line argument +* : +* : * +* ----------------- : +* license : Lightweight profiler library for c++ +* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin +* : +* : +* : Licensed under 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. +* : +* : +* : GNU General Public License Usage +* : Alternatively, this file may be used 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 . +************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main_window.h" +#include "blocks_tree_widget.h" +#include "blocks_graphics_view.h" +#include "descriptors_tree_widget.h" +#include "globals.h" +#include "easy/easy_net.h" + +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +////////////////////////////////////////////////////////////////////////// + +const int LOADER_TIMER_INTERVAL = 40; +const auto NETWORK_CACHE_FILE = "easy_profiler_stream.cache"; + +////////////////////////////////////////////////////////////////////////// + +inline void clear_stream(std::stringstream& _stream) +{ +#if defined(__GNUC__) && __GNUC__ < 5 + // gcc 4 has a known bug which has been solved in gcc 5: + // std::stringstream has no swap() method :( + _stream.str(std::string()); +#else + std::stringstream().swap(_stream); +#endif +} + +////////////////////////////////////////////////////////////////////////// + +EasyMainWindow::EasyMainWindow() : Parent(), m_lastAddress("localhost"), m_lastPort(::profiler::DEFAULT_PORT) +{ + { QIcon icon(":/logo"); if (!icon.isNull()) QApplication::setWindowIcon(icon); } + + setObjectName("ProfilerGUI_MainWindow"); + setWindowTitle("EasyProfiler"); + setDockNestingEnabled(true); + setAcceptDrops(true); + resize(800, 600); + + setStatusBar(new QStatusBar()); + + m_graphicsView = new QDockWidget("Diagram", this); + m_graphicsView->setObjectName("ProfilerGUI_Diagram"); + m_graphicsView->setMinimumHeight(50); + m_graphicsView->setAllowedAreas(Qt::AllDockWidgetAreas); + + auto graphicsView = new EasyGraphicsViewWidget(this); + m_graphicsView->setWidget(graphicsView); + + m_treeWidget = new QDockWidget("Hierarchy", this); + m_treeWidget->setObjectName("ProfilerGUI_Hierarchy"); + m_treeWidget->setMinimumHeight(50); + m_treeWidget->setAllowedAreas(Qt::AllDockWidgetAreas); + + auto treeWidget = new EasyHierarchyWidget(this); + m_treeWidget->setWidget(treeWidget); + + addDockWidget(Qt::TopDockWidgetArea, m_graphicsView); + addDockWidget(Qt::BottomDockWidgetArea, m_treeWidget); + +#if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 + auto descTree = new EasyDescWidget(); + m_descTreeWidget = new QDockWidget("Blocks"); + m_descTreeWidget->setObjectName("ProfilerGUI_Blocks"); + m_descTreeWidget->setMinimumHeight(50); + m_descTreeWidget->setAllowedAreas(Qt::AllDockWidgetAreas); + m_descTreeWidget->setWidget(descTree); + addDockWidget(Qt::BottomDockWidgetArea, m_descTreeWidget); +#endif + + + loadSettings(); + + + auto toolbar = addToolBar("FileToolbar"); + toolbar->setIconSize(::profiler_gui::ICONS_SIZE); + toolbar->setObjectName("ProfilerGUI_FileToolbar"); + toolbar->setContentsMargins(1, 0, 1, 0); + + m_loadActionMenu = new QMenu(this); + auto action = m_loadActionMenu->menuAction(); + action->setText("Open file"); + action->setIcon(QIcon(":/Open")); + connect(action, &QAction::triggered, this, &This::onOpenFileClicked); + toolbar->addAction(action); + + for (const auto& f : m_lastFiles) + { + action = new QAction(f, this); + connect(action, &QAction::triggered, this, &This::onOpenFileClicked); + m_loadActionMenu->addAction(action); + } + + m_saveAction = toolbar->addAction(QIcon(":/Save"), tr("Save"), this, SLOT(onSaveFileClicked(bool))); + m_deleteAction = toolbar->addAction(QIcon(":/Delete"), tr("Clear all"), this, SLOT(onDeleteClicked(bool))); + + m_saveAction->setEnabled(false); + m_deleteAction->setEnabled(false); + + + + toolbar = addToolBar("ProfileToolbar"); + toolbar->setIconSize(::profiler_gui::ICONS_SIZE); + toolbar->setObjectName("ProfilerGUI_ProfileToolbar"); + toolbar->setContentsMargins(1, 0, 1, 0); + + toolbar->addAction(QIcon(":/List"), tr("Blocks"), this, SLOT(onEditBlocksClicked(bool))); + m_captureAction = toolbar->addAction(QIcon(":/Start"), tr("Capture"), this, SLOT(onCaptureClicked(bool))); + m_captureAction->setEnabled(false); + + toolbar->addSeparator(); + m_connectAction = toolbar->addAction(QIcon(":/Connection"), tr("Connect"), this, SLOT(onConnectClicked(bool))); + + auto lbl = new QLabel("Address:", toolbar); + lbl->setContentsMargins(5, 0, 2, 0); + toolbar->addWidget(lbl); + m_addressEdit = new QLineEdit(); + m_addressEdit->setToolTip("Enter IP-address or host name"); + //QRegExp rx("^0*(2(5[0-5]|[0-4]\\d)|1?\\d{1,2})(\\.0*(2(5[0-5]|[0-4]\\d)|1?\\d{1,2})){3}$"); + //m_addressEdit->setValidator(new QRegExpValidator(rx, m_addressEdit)); + m_addressEdit->setText(m_lastAddress); + m_addressEdit->setFixedWidth((m_addressEdit->fontMetrics().width(QString("255.255.255.255")) * 3) / 2); + toolbar->addWidget(m_addressEdit); + + lbl = new QLabel("Port:", toolbar); + lbl->setContentsMargins(5, 0, 2, 0); + toolbar->addWidget(lbl); + m_portEdit = new QLineEdit(); + m_portEdit->setValidator(new QIntValidator(1, 65535, m_portEdit)); + m_portEdit->setText(QString::number(m_lastPort)); + m_portEdit->setFixedWidth(m_portEdit->fontMetrics().width(QString("000000")) + 10); + toolbar->addWidget(m_portEdit); + + connect(m_addressEdit, &QLineEdit::returnPressed, [this](){ onConnectClicked(true); }); + connect(m_portEdit, &QLineEdit::returnPressed, [this](){ onConnectClicked(true); }); + + + + toolbar = addToolBar("SetupToolbar"); + toolbar->setIconSize(::profiler_gui::ICONS_SIZE); + toolbar->setObjectName("ProfilerGUI_SetupToolbar"); + toolbar->setContentsMargins(1, 0, 1, 0); + + toolbar->addAction(QIcon(":/Expand"), "Expand all", this, SLOT(onExpandAllClicked(bool))); + toolbar->addAction(QIcon(":/Collapse"), "Collapse all", this, SLOT(onCollapseAllClicked(bool))); + + toolbar->addSeparator(); + auto menu = new QMenu("Settings", this); + QToolButton* toolButton = new QToolButton(toolbar); + toolButton->setIcon(QIcon(":/Settings")); + toolButton->setMenu(menu); + toolButton->setPopupMode(QToolButton::InstantPopup); + toolbar->addWidget(toolButton); + + action = menu->addAction("Statistics enabled"); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.enable_statistics); + connect(action, &QAction::triggered, this, &This::onEnableDisableStatistics); + if (EASY_GLOBALS.enable_statistics) + { + auto f = action->font(); + f.setBold(true); + action->setFont(f); + SET_ICON(action, ":/Stats"); + } + else + { + action->setText("Statistics disabled"); + SET_ICON(action, ":/Stats-off"); + } + + + menu->addSeparator(); + auto submenu = menu->addMenu("View"); + submenu->setToolTipsVisible(true); + action = submenu->addAction("Draw items' borders"); + action->setToolTip("Draw borders for blocks on diagram.\nThis reduces performance."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.draw_graphics_items_borders); + connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.draw_graphics_items_borders = _checked; refreshDiagram(); }); + + action = submenu->addAction("Overlap narrow children"); + action->setToolTip("Children blocks will be overlaped by narrow\nparent blocks. See also \'Blocks narrow size\'.\nThis improves performance."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.hide_narrow_children); + connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.hide_narrow_children = _checked; refreshDiagram(); }); + + action = submenu->addAction("Hide min-size blocks"); + action->setToolTip("Hides blocks which screen size\nis less than \'Min blocks size\'."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.hide_minsize_blocks); + connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.hide_minsize_blocks = _checked; refreshDiagram(); }); + + action = submenu->addAction("Build hierarchy only for current thread"); + action->setToolTip("Hierarchy tree will be built\nfor blocks from current thread only.\nThis improves performance\nand saves a lot of memory."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.only_current_thread_hierarchy); + connect(action, &QAction::triggered, this, &This::onHierarchyFlagChange); + + action = submenu->addAction("Add zero blocks to hierarchy"); + action->setToolTip("Zero duration blocks will be added into hierarchy tree.\nThis reduces performance and increases memory consumption."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.add_zero_blocks_to_hierarchy); + connect(action, &QAction::triggered, [this](bool _checked) + { + EASY_GLOBALS.add_zero_blocks_to_hierarchy = _checked; + emit EASY_GLOBALS.events.hierarchyFlagChanged(_checked); + }); + + action = submenu->addAction("Enable zero duration blocks on diagram"); + action->setToolTip("If checked then allows diagram to paint zero duration blocks\nwith 1px width on each scale. Otherwise, such blocks will be resized\nto 250ns duration."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.enable_zero_length); + connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.enable_zero_length = _checked; refreshDiagram(); }); + + action = submenu->addAction("Highlight similar blocks"); + action->setToolTip("Highlight all visible blocks which are similar\nto the current selected block.\nThis reduces performance."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.highlight_blocks_with_same_id); + connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.highlight_blocks_with_same_id = _checked; refreshDiagram(); }); + + action = submenu->addAction("Collapse blocks on tree reset"); + action->setToolTip("This collapses all blocks on diagram\nafter hierarchy tree reset."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.collapse_items_on_tree_close); + connect(action, &QAction::triggered, this, &This::onCollapseItemsAfterCloseChanged); + + action = submenu->addAction("Expand all on file open"); + action->setToolTip("If checked then all blocks on diagram\nwill be initially expanded."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.all_items_expanded_by_default); + connect(action, &QAction::triggered, this, &This::onAllItemsExpandedByDefaultChange); + + action = submenu->addAction("Bind diagram and tree expand"); + action->setToolTip("Expanding/collapsing blocks at diagram expands/collapses\nblocks at hierarchy tree and wise versa."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.bind_scene_and_tree_expand_status); + connect(action, &QAction::triggered, this, &This::onBindExpandStatusChange); + + action = submenu->addAction("Selecting block changes current thread"); + action->setToolTip("Automatically select thread while selecting a block.\nIf not checked then you will have to select current thread\nmanually double clicking on thread name on a diagram."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.selecting_block_changes_thread); + connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.selecting_block_changes_thread = _checked; }); + + action = submenu->addAction("Draw event indicators"); + action->setToolTip("Display event indicators under the blocks\n(even if event-blocks are not visible).\nThis slightly reduces performance."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.enable_event_indicators); + connect(action, &QAction::triggered, this, &This::onEventIndicatorsChange); + + action = submenu->addAction("Use decorated thread names"); + action->setToolTip("Add \'Thread\' word into thread name if there is no one already.\nExamples: \'Render\' will change to \'Render Thread\'\n\'WorkerThread\' will not change."); + action->setCheckable(true); + action->setChecked(EASY_GLOBALS.use_decorated_thread_name); + connect(action, &QAction::triggered, [this](bool _checked) + { + EASY_GLOBALS.use_decorated_thread_name = _checked; + emit EASY_GLOBALS.events.threadNameDecorationChanged(); + }); + + submenu->addSeparator(); + auto actionGroup = new QActionGroup(this); + actionGroup->setExclusive(true); + + action = new QAction("Chrono text at top", actionGroup); + action->setToolTip("Draw duration of selected interval\nat the top of the screen."); + action->setCheckable(true); + action->setData(static_cast(::profiler_gui::ChronoTextPosition_Top)); + if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Top) + action->setChecked(true); + submenu->addAction(action); + connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged); + + action = new QAction("Chrono text at center", actionGroup); + action->setToolTip("Draw duration of selected interval\nat the center of the screen."); + action->setCheckable(true); + action->setData(static_cast(::profiler_gui::ChronoTextPosition_Center)); + if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Center) + action->setChecked(true); + submenu->addAction(action); + connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged); + + action = new QAction("Chrono text at bottom", actionGroup); + action->setToolTip("Draw duration of selected interval\nat the bottom of the screen."); + action->setCheckable(true); + action->setData(static_cast(::profiler_gui::ChronoTextPosition_Bottom)); + if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Bottom) + action->setChecked(true); + submenu->addAction(action); + connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged); + + submenu->addSeparator(); + auto w = new QWidget(submenu); + auto l = new QHBoxLayout(w); + l->setContentsMargins(33, 1, 1, 1); + l->addWidget(new QLabel("Min blocks spacing, px", w), 0, Qt::AlignLeft); + auto spinbox = new QSpinBox(w); + spinbox->setMinimum(0); + spinbox->setValue(EASY_GLOBALS.blocks_spacing); + spinbox->setFixedWidth(50); + connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onSpacingChange(int))); + l->addWidget(spinbox); + w->setLayout(l); + auto waction = new QWidgetAction(submenu); + waction->setDefaultWidget(w); + submenu->addAction(waction); + + w = new QWidget(submenu); + l = new QHBoxLayout(w); + l->setContentsMargins(33, 1, 1, 1); + l->addWidget(new QLabel("Min blocks size, px", w), 0, Qt::AlignLeft); + spinbox = new QSpinBox(w); + spinbox->setMinimum(1); + spinbox->setValue(EASY_GLOBALS.blocks_size_min); + spinbox->setFixedWidth(50); + connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onMinSizeChange(int))); + l->addWidget(spinbox); + w->setLayout(l); + waction = new QWidgetAction(submenu); + waction->setDefaultWidget(w); + submenu->addAction(waction); + + w = new QWidget(submenu); + l = new QHBoxLayout(w); + l->setContentsMargins(33, 1, 1, 1); + l->addWidget(new QLabel("Blocks narrow size, px", w), 0, Qt::AlignLeft); + spinbox = new QSpinBox(w); + spinbox->setMinimum(1); + spinbox->setValue(EASY_GLOBALS.blocks_narrow_size); + spinbox->setFixedWidth(50); + connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onNarrowSizeChange(int))); + l->addWidget(spinbox); + w->setLayout(l); + waction = new QWidgetAction(submenu); + waction->setDefaultWidget(w); + submenu->addAction(waction); + + + submenu = menu->addMenu("Units"); + actionGroup = new QActionGroup(this); + actionGroup->setExclusive(true); + action = new QAction("Auto", actionGroup); + action->setCheckable(true); + action->setData(static_cast(::profiler_gui::TimeUnits_auto)); + if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_auto) + action->setChecked(true); + submenu->addAction(action); + connect(action, &QAction::triggered, this, &This::onUnitsChanged); + + action = new QAction("Milliseconds", actionGroup); + action->setCheckable(true); + action->setData(static_cast(::profiler_gui::TimeUnits_ms)); + if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_ms) + action->setChecked(true); + submenu->addAction(action); + connect(action, &QAction::triggered, this, &This::onUnitsChanged); + + action = new QAction("Microseconds", actionGroup); + action->setCheckable(true); + action->setData(static_cast(::profiler_gui::TimeUnits_us)); + if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_us) + action->setChecked(true); + submenu->addAction(action); + connect(action, &QAction::triggered, this, &This::onUnitsChanged); + + action = new QAction("Nanoseconds", actionGroup); + action->setCheckable(true); + action->setData(static_cast(::profiler_gui::TimeUnits_ns)); + if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_ns) + action->setChecked(true); + submenu->addAction(action); + connect(action, &QAction::triggered, this, &This::onUnitsChanged); + + + submenu = menu->addMenu("Remote"); + m_eventTracingEnableAction = submenu->addAction("Event tracing enabled"); + m_eventTracingEnableAction->setCheckable(true); + m_eventTracingEnableAction->setEnabled(false); + connect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange); + + m_eventTracingPriorityAction = submenu->addAction("Low priority event tracing"); + m_eventTracingPriorityAction->setCheckable(true); + m_eventTracingPriorityAction->setChecked(EASY_OPTION_LOW_PRIORITY_EVENT_TRACING); + m_eventTracingPriorityAction->setEnabled(false); + connect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange); + + + submenu = menu->addMenu("Encoding"); + actionGroup = new QActionGroup(this); + actionGroup->setExclusive(true); + + auto default_codec_mib = QTextCodec::codecForLocale()->mibEnum(); + foreach(int mib, QTextCodec::availableMibs()) + { + auto codec = QTextCodec::codecForMib(mib)->name(); + + action = new QAction(codec, actionGroup); + action->setCheckable(true); + if (mib == default_codec_mib) + action->setChecked(true); + + submenu->addAction(action); + connect(action, &QAction::triggered, this, &This::onEncodingChanged); + } + + auto tb_height = toolbar->height() + 4; + toolbar = addToolBar("FrameToolbar"); + toolbar->setIconSize(::profiler_gui::ICONS_SIZE); + toolbar->setObjectName("ProfilerGUI_FrameToolbar"); + toolbar->setContentsMargins(1, 0, 1, 0); + toolbar->setMinimumHeight(tb_height); + + lbl = new QLabel("Frame time:", toolbar); + lbl->setContentsMargins(5, 2, 2, 2); + toolbar->addWidget(lbl); + + m_frameTimeEdit = new QLineEdit(); + m_frameTimeEdit->setFixedWidth(70); + auto val = new QDoubleValidator(m_frameTimeEdit); + val->setLocale(QLocale::c()); + val->setBottom(0); + m_frameTimeEdit->setValidator(val); + m_frameTimeEdit->setText(QString::number(EASY_GLOBALS.frame_time * 1e-3)); + connect(m_frameTimeEdit, &QLineEdit::editingFinished, this, &This::onFrameTimeEditFinish); + toolbar->addWidget(m_frameTimeEdit); + + lbl = new QLabel("ms", toolbar); + lbl->setContentsMargins(5, 2, 1, 1); + toolbar->addWidget(lbl); + + + connect(graphicsView->view(), &EasyGraphicsView::intervalChanged, treeWidget->tree(), &EasyTreeWidget::setTreeBlocks); + connect(&m_readerTimer, &QTimer::timeout, this, &This::onFileReaderTimeout); + connect(&m_listenerTimer, &QTimer::timeout, this, &This::onListenerTimerTimeout); + + + m_progress = new QProgressDialog("Loading file...", "Cancel", 0, 100, this); + m_progress->setFixedWidth(300); + m_progress->setWindowTitle("EasyProfiler"); + m_progress->setModal(true); + m_progress->setValue(100); + //m_progress->hide(); + connect(m_progress, &QProgressDialog::canceled, this, &This::onFileReaderCancel); + + loadGeometry(); + + if(QCoreApplication::arguments().size() > 1) + { + auto opened_filename = QCoreApplication::arguments().at(1); + loadFile(opened_filename); + } + + connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blockStatusChanged, this, &This::onBlockStatusChange); + connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blocksRefreshRequired, this, &This::onGetBlockDescriptionsClicked); +} + +EasyMainWindow::~EasyMainWindow() +{ + delete m_progress; +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::dragEnterEvent(QDragEnterEvent* drag_event) +{ + if (drag_event->mimeData()->hasUrls()) + drag_event->acceptProposedAction(); +} + +void EasyMainWindow::dragMoveEvent(QDragMoveEvent* drag_event) +{ + if (drag_event->mimeData()->hasUrls()) + drag_event->acceptProposedAction(); +} + +void EasyMainWindow::dragLeaveEvent(QDragLeaveEvent* drag_event) +{ + drag_event->accept(); +} + +void EasyMainWindow::dropEvent(QDropEvent* drop_event) +{ + const auto& urls = drop_event->mimeData()->urls(); + if (!urls.empty()) + loadFile(urls.front().toLocalFile()); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onOpenFileClicked(bool) +{ + auto action = qobject_cast(sender()); + if (action == nullptr) + return; + + if (action == m_loadActionMenu->menuAction()) + { + auto filename = QFileDialog::getOpenFileName(this, "Open profiler log", m_lastFiles.empty() ? QString() : m_lastFiles.front(), "Profiler Log File (*.prof);;All Files (*.*)"); + if (!filename.isEmpty()) + loadFile(filename); + } + else + { + loadFile(action->text()); + } +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::addFileToList(const QString& filename) +{ + m_lastFiles.push_front(filename); + + auto action = new QAction(filename, this); + connect(action, &QAction::triggered, this, &This::onOpenFileClicked); + auto fileActions = m_loadActionMenu->actions(); + if (fileActions.empty()) + m_loadActionMenu->addAction(action); + else + m_loadActionMenu->insertAction(fileActions.front(), action); + + if (m_lastFiles.size() > 10) + { + // Keep 10 files at the list + m_lastFiles.pop_back(); + m_loadActionMenu->removeAction(fileActions.back()); + delete fileActions.back(); + } +} + +void EasyMainWindow::loadFile(const QString& filename) +{ + const auto i = filename.lastIndexOf(QChar('/')); + const auto j = filename.lastIndexOf(QChar('\\')); + m_progress->setLabelText(QString("Loading %1...").arg(filename.mid(::std::max(i, j) + 1))); + + m_progress->setValue(0); + m_progress->show(); + m_readerTimer.start(LOADER_TIMER_INTERVAL); + m_reader.load(filename); +} + +void EasyMainWindow::readStream(::std::stringstream& data) +{ + m_progress->setLabelText(tr("Reading from stream...")); + + m_progress->setValue(0); + m_progress->show(); + m_readerTimer.start(LOADER_TIMER_INTERVAL); + m_reader.load(data); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onSaveFileClicked(bool) +{ + if (m_serializedBlocks.empty()) + return; + + QString lastFile = m_lastFiles.empty() ? QString() : m_lastFiles.front(); + + const auto i = lastFile.lastIndexOf(QChar('/')); + const auto j = lastFile.lastIndexOf(QChar('\\')); + auto k = ::std::max(i, j); + + QString dir; + if (k > 0) + dir = lastFile.mid(0, ++k); + + auto filename = QFileDialog::getSaveFileName(this, "Save profiler log", dir, "Profiler Log File (*.prof);;All Files (*.*)"); + if (!filename.isEmpty()) + { + bool inOk = false, outOk = false; + int8_t retry1 = -1; + while (++retry1 < 4) + { + ::std::ifstream inFile(m_bNetworkFileRegime ? NETWORK_CACHE_FILE : lastFile.toStdString().c_str(), ::std::fstream::binary); + if (!inFile.is_open()) + { + ::std::this_thread::sleep_for(::std::chrono::milliseconds(500)); + continue; + } + + inOk = true; + + int8_t retry2 = -1; + while (++retry2 < 4) + { + ::std::ofstream outFile(filename.toStdString(), ::std::fstream::binary); + if (!outFile.is_open()) + { + ::std::this_thread::sleep_for(::std::chrono::milliseconds(500)); + continue; + } + + outFile << inFile.rdbuf(); + outOk = true; + break; + } + + break; + } + + if (outOk) + { + if (m_bNetworkFileRegime) + QFile::remove(QString(NETWORK_CACHE_FILE)); + addFileToList(filename); + m_bNetworkFileRegime = false; + } + else if (inOk) + { + QMessageBox::warning(this, "Warning", "Can not open destination file.\nSaving incomplete.", QMessageBox::Close); + } + else + { + if (m_bNetworkFileRegime) + QMessageBox::warning(this, "Warning", "Can not open network cache file.\nSaving incomplete.", QMessageBox::Close); + else + QMessageBox::warning(this, "Warning", "Can not open source file.\nSaving incomplete.", QMessageBox::Close); + } + } +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::clear() +{ + static_cast(m_treeWidget->widget())->clear(true); + static_cast(m_graphicsView->widget())->clear(); + +#if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 + static_cast(m_descTreeWidget->widget())->clear(); +#endif + if (m_dialogDescTree != nullptr) + m_dialogDescTree->clear(); + + EASY_GLOBALS.selected_thread = 0; + ::profiler_gui::set_max(EASY_GLOBALS.selected_block); + ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id); + EASY_GLOBALS.profiler_blocks.clear(); + EASY_GLOBALS.descriptors.clear(); + EASY_GLOBALS.gui_blocks.clear(); + + m_serializedBlocks.clear(); + m_serializedDescriptors.clear(); + + m_saveAction->setEnabled(false); + m_deleteAction->setEnabled(false); + + m_bNetworkFileRegime = false; +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::refreshDiagram() +{ + static_cast(m_graphicsView->widget())->view()->scene()->update(); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onDeleteClicked(bool) +{ + auto button = QMessageBox::question(this, "Clear all profiled data", "All profiled data is going to be deleted!\nContinue?", QMessageBox::Yes, QMessageBox::No); + if (button == QMessageBox::Yes) + clear(); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onExitClicked(bool) +{ + close(); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onEncodingChanged(bool) +{ + auto _sender = qobject_cast(sender()); + auto name = _sender->text(); + QTextCodec *codec = QTextCodec::codecForName(name.toStdString().c_str()); + QTextCodec::setCodecForLocale(codec); +} + +void EasyMainWindow::onChronoTextPosChanged(bool) +{ + auto _sender = qobject_cast(sender()); + EASY_GLOBALS.chrono_text_position = static_cast<::profiler_gui::ChronometerTextPosition>(_sender->data().toInt()); + refreshDiagram(); +} + +void EasyMainWindow::onUnitsChanged(bool) +{ + auto _sender = qobject_cast(sender()); + EASY_GLOBALS.time_units = static_cast<::profiler_gui::TimeUnits>(_sender->data().toInt()); +} + +void EasyMainWindow::onEventIndicatorsChange(bool _checked) +{ + EASY_GLOBALS.enable_event_indicators = _checked; + refreshDiagram(); +} + +void EasyMainWindow::onEnableDisableStatistics(bool _checked) +{ + EASY_GLOBALS.enable_statistics = _checked; + + auto action = qobject_cast(sender()); + if (action != nullptr) + { + auto f = action->font(); + f.setBold(_checked); + action->setFont(f); + + if (_checked) + { + action->setText("Statistics enabled"); + SET_ICON(action, ":/Stats"); + } + else + { + action->setText("Statistics disabled"); + SET_ICON(action, ":/Stats-off"); + } + } +} + +void EasyMainWindow::onCollapseItemsAfterCloseChanged(bool _checked) +{ + EASY_GLOBALS.collapse_items_on_tree_close = _checked; +} + +void EasyMainWindow::onAllItemsExpandedByDefaultChange(bool _checked) +{ + EASY_GLOBALS.all_items_expanded_by_default = _checked; +} + +void EasyMainWindow::onBindExpandStatusChange(bool _checked) +{ + EASY_GLOBALS.bind_scene_and_tree_expand_status = _checked; +} + +void EasyMainWindow::onHierarchyFlagChange(bool _checked) +{ + EASY_GLOBALS.only_current_thread_hierarchy = _checked; + emit EASY_GLOBALS.events.hierarchyFlagChanged(_checked); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onExpandAllClicked(bool) +{ + for (auto& block : EASY_GLOBALS.gui_blocks) + block.expanded = true; + + emit EASY_GLOBALS.events.itemsExpandStateChanged(); + + auto tree = static_cast(m_treeWidget->widget())->tree(); + const QSignalBlocker b(tree); + tree->expandAll(); +} + +void EasyMainWindow::onCollapseAllClicked(bool) +{ + for (auto& block : EASY_GLOBALS.gui_blocks) + block.expanded = false; + + emit EASY_GLOBALS.events.itemsExpandStateChanged(); + + auto tree = static_cast(m_treeWidget->widget())->tree(); + const QSignalBlocker b(tree); + tree->collapseAll(); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onSpacingChange(int _value) +{ + EASY_GLOBALS.blocks_spacing = _value; + refreshDiagram(); +} + +void EasyMainWindow::onMinSizeChange(int _value) +{ + EASY_GLOBALS.blocks_size_min = _value; + refreshDiagram(); +} + +void EasyMainWindow::onNarrowSizeChange(int _value) +{ + EASY_GLOBALS.blocks_narrow_size = _value; + refreshDiagram(); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onEditBlocksClicked(bool) +{ + if (m_descTreeDialog != nullptr) + { + m_descTreeDialog->raise(); + return; + } + + m_descTreeDialog = new QDialog(); + m_descTreeDialog->setAttribute(Qt::WA_DeleteOnClose, true); + m_descTreeDialog->setWindowTitle("EasyProfiler"); + m_descTreeDialog->resize(800, 600); + connect(m_descTreeDialog, &QDialog::finished, this, &This::onDescTreeDialogClose); + + auto l = new QVBoxLayout(m_descTreeDialog); + m_dialogDescTree = new EasyDescWidget(m_descTreeDialog); + l->addWidget(m_dialogDescTree); + m_descTreeDialog->setLayout(l); + + m_dialogDescTree->build(); + m_descTreeDialog->show(); +} + +void EasyMainWindow::onDescTreeDialogClose(int) +{ + disconnect(m_descTreeDialog, &QDialog::finished, this, &This::onDescTreeDialogClose); + m_dialogDescTree = nullptr; + m_descTreeDialog = nullptr; +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::closeEvent(QCloseEvent* close_event) +{ + saveSettingsAndGeometry(); + + if (m_descTreeDialog != nullptr) + { + m_descTreeDialog->reject(); + m_descTreeDialog = nullptr; + m_dialogDescTree = nullptr; + } + + Parent::closeEvent(close_event); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::loadSettings() +{ + QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME); + settings.beginGroup("main"); + + auto last_files = settings.value("last_files"); + if (!last_files.isNull()) + m_lastFiles = last_files.toStringList(); + + auto last_addr = settings.value("ip_address"); + if (!last_addr.isNull()) + m_lastAddress = last_addr.toString(); + + auto last_port = settings.value("port"); + if (!last_port.isNull()) + m_lastPort = (uint16_t)last_port.toUInt(); + + + auto val = settings.value("chrono_text_position"); + if (!val.isNull()) + EASY_GLOBALS.chrono_text_position = static_cast<::profiler_gui::ChronometerTextPosition>(val.toInt()); + + val = settings.value("time_units"); + if (!val.isNull()) + EASY_GLOBALS.time_units = static_cast<::profiler_gui::TimeUnits>(val.toInt()); + + + val = settings.value("frame_time"); + if (!val.isNull()) + EASY_GLOBALS.frame_time = val.toFloat(); + + val = settings.value("blocks_spacing"); + if (!val.isNull()) + EASY_GLOBALS.blocks_spacing = val.toInt(); + + val = settings.value("blocks_size_min"); + if (!val.isNull()) + EASY_GLOBALS.blocks_size_min = val.toInt(); + + val = settings.value("blocks_narrow_size"); + if (!val.isNull()) + EASY_GLOBALS.blocks_narrow_size = val.toInt(); + + + auto flag = settings.value("draw_graphics_items_borders"); + if (!flag.isNull()) + EASY_GLOBALS.draw_graphics_items_borders = flag.toBool(); + + flag = settings.value("hide_narrow_children"); + if (!flag.isNull()) + EASY_GLOBALS.hide_narrow_children = flag.toBool(); + + flag = settings.value("hide_minsize_blocks"); + if (!flag.isNull()) + EASY_GLOBALS.hide_minsize_blocks = flag.toBool(); + + flag = settings.value("collapse_items_on_tree_close"); + if (!flag.isNull()) + EASY_GLOBALS.collapse_items_on_tree_close = flag.toBool(); + + flag = settings.value("all_items_expanded_by_default"); + if (!flag.isNull()) + EASY_GLOBALS.all_items_expanded_by_default = flag.toBool(); + + flag = settings.value("only_current_thread_hierarchy"); + if (!flag.isNull()) + EASY_GLOBALS.only_current_thread_hierarchy = flag.toBool(); + + flag = settings.value("enable_zero_length"); + if (!flag.isNull()) + EASY_GLOBALS.enable_zero_length = flag.toBool(); + + flag = settings.value("add_zero_blocks_to_hierarchy"); + if (!flag.isNull()) + EASY_GLOBALS.add_zero_blocks_to_hierarchy = flag.toBool(); + + + flag = settings.value("highlight_blocks_with_same_id"); + if (!flag.isNull()) + EASY_GLOBALS.highlight_blocks_with_same_id = flag.toBool(); + + flag = settings.value("bind_scene_and_tree_expand_status"); + if (!flag.isNull()) + EASY_GLOBALS.bind_scene_and_tree_expand_status = flag.toBool(); + + flag = settings.value("selecting_block_changes_thread"); + if (!flag.isNull()) + EASY_GLOBALS.selecting_block_changes_thread = flag.toBool(); + + flag = settings.value("enable_event_indicators"); + if (!flag.isNull()) + EASY_GLOBALS.enable_event_indicators = flag.toBool(); + + flag = settings.value("use_decorated_thread_name"); + if (!flag.isNull()) + EASY_GLOBALS.use_decorated_thread_name = flag.toBool(); + + flag = settings.value("enable_statistics"); + if (!flag.isNull()) + EASY_GLOBALS.enable_statistics = flag.toBool(); + + QString encoding = settings.value("encoding", "UTF-8").toString(); + auto default_codec_mib = QTextCodec::codecForName(encoding.toStdString().c_str())->mibEnum(); + auto default_codec = QTextCodec::codecForMib(default_codec_mib); + QTextCodec::setCodecForLocale(default_codec); + + settings.endGroup(); +} + +void EasyMainWindow::loadGeometry() +{ + QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME); + settings.beginGroup("main"); + + auto geometry = settings.value("geometry").toByteArray(); + if (!geometry.isEmpty()) + restoreGeometry(geometry); + + auto state = settings.value("windowState").toByteArray(); + if (!state.isEmpty()) + restoreState(state); + + settings.endGroup(); +} + +void EasyMainWindow::saveSettingsAndGeometry() +{ + QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME); + settings.beginGroup("main"); + + settings.setValue("geometry", this->saveGeometry()); + settings.setValue("windowState", this->saveState()); + settings.setValue("last_files", m_lastFiles); + settings.setValue("ip_address", m_lastAddress); + settings.setValue("port", (quint32)m_lastPort); + settings.setValue("chrono_text_position", static_cast(EASY_GLOBALS.chrono_text_position)); + settings.setValue("time_units", static_cast(EASY_GLOBALS.time_units)); + settings.setValue("frame_time", EASY_GLOBALS.frame_time); + settings.setValue("blocks_spacing", EASY_GLOBALS.blocks_spacing); + settings.setValue("blocks_size_min", EASY_GLOBALS.blocks_size_min); + settings.setValue("blocks_narrow_size", EASY_GLOBALS.blocks_narrow_size); + settings.setValue("draw_graphics_items_borders", EASY_GLOBALS.draw_graphics_items_borders); + settings.setValue("hide_narrow_children", EASY_GLOBALS.hide_narrow_children); + settings.setValue("hide_minsize_blocks", EASY_GLOBALS.hide_minsize_blocks); + settings.setValue("collapse_items_on_tree_close", EASY_GLOBALS.collapse_items_on_tree_close); + settings.setValue("all_items_expanded_by_default", EASY_GLOBALS.all_items_expanded_by_default); + settings.setValue("only_current_thread_hierarchy", EASY_GLOBALS.only_current_thread_hierarchy); + settings.setValue("enable_zero_length", EASY_GLOBALS.enable_zero_length); + settings.setValue("add_zero_blocks_to_hierarchy", EASY_GLOBALS.add_zero_blocks_to_hierarchy); + settings.setValue("highlight_blocks_with_same_id", EASY_GLOBALS.highlight_blocks_with_same_id); + settings.setValue("bind_scene_and_tree_expand_status", EASY_GLOBALS.bind_scene_and_tree_expand_status); + settings.setValue("selecting_block_changes_thread", EASY_GLOBALS.selecting_block_changes_thread); + settings.setValue("enable_event_indicators", EASY_GLOBALS.enable_event_indicators); + settings.setValue("use_decorated_thread_name", EASY_GLOBALS.use_decorated_thread_name); + settings.setValue("enable_statistics", EASY_GLOBALS.enable_statistics); + settings.setValue("encoding", QTextCodec::codecForLocale()->name()); + + settings.endGroup(); +} + +void EasyMainWindow::setDisconnected(bool _showMessage) +{ + if (_showMessage) + QMessageBox::warning(this, "Warning", "Application was disconnected", QMessageBox::Close); + + EASY_GLOBALS.connected = false; + m_captureAction->setEnabled(false); + SET_ICON(m_connectAction, ":/Connection"); + + m_eventTracingEnableAction->setEnabled(false); + m_eventTracingPriorityAction->setEnabled(false); + + emit EASY_GLOBALS.events.connectionChanged(false); + +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onListenerTimerTimeout() +{ + if (!m_listener.connected()) + m_listenerDialog->reject(); +} + +void EasyMainWindow::onListenerDialogClose(int) +{ + m_listenerTimer.stop(); + disconnect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose); + m_listenerDialog = nullptr; + + switch (m_listener.regime()) + { + case LISTENER_CAPTURE: + { + m_listenerDialog = new QMessageBox(QMessageBox::Information, "Receiving data...", "This process may take some time.", QMessageBox::NoButton, this); + m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true); + m_listenerDialog->show(); + + m_listener.stopCapture(); + + m_listenerDialog->reject(); + m_listenerDialog = nullptr; + + if (m_listener.size() != 0) + { + readStream(m_listener.data()); + m_listener.clearData(); + } + + break; + } + + case LISTENER_DESCRIBE: + { + break; + } + + default: + return; + } + + if (!m_listener.connected()) + { + setDisconnected(); + } +} + + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onFileReaderTimeout() +{ + if (m_reader.done()) + { + auto nblocks = m_reader.size(); + if (nblocks != 0) + { + static_cast(m_treeWidget->widget())->clear(true); + + ::profiler::SerializedData serialized_blocks; + ::profiler::SerializedData serialized_descriptors; + ::profiler::descriptors_list_t descriptors; + ::profiler::blocks_t blocks; + ::profiler::thread_blocks_tree_t threads_map; + QString filename; + uint32_t descriptorsNumberInFile = 0; + m_reader.get(serialized_blocks, serialized_descriptors, descriptors, blocks, threads_map, descriptorsNumberInFile, filename); + + if (threads_map.size() > 0xff) + { + if (m_reader.isFile()) + qWarning() << "Warning: file " << filename << " contains " << threads_map.size() << " threads!"; + else + qWarning() << "Warning: input stream contains " << threads_map.size() << " threads!"; + qWarning() << "Warning: Currently, maximum number of displayed threads is 255! Some threads will not be displayed."; + } + + m_bNetworkFileRegime = !m_reader.isFile(); + if (!m_bNetworkFileRegime) + { + auto index = m_lastFiles.indexOf(filename, 0); + if (index == -1) + { + // This file is totally new. Add it to the list. + addFileToList(filename); + } + else if (index != 0) + { + // This file has been already loaded. Move it to the front. + m_lastFiles.move(index, 0); + auto fileActions = m_loadActionMenu->actions(); + auto action = fileActions.at(index); + m_loadActionMenu->removeAction(action); + m_loadActionMenu->insertAction(fileActions.front(), action); + } + } + m_serializedBlocks = ::std::move(serialized_blocks); + m_serializedDescriptors = ::std::move(serialized_descriptors); + m_descriptorsNumberInFile = descriptorsNumberInFile; + EASY_GLOBALS.selected_thread = 0; + ::profiler_gui::set_max(EASY_GLOBALS.selected_block); + ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id); + EASY_GLOBALS.profiler_blocks.swap(threads_map); + EASY_GLOBALS.descriptors.swap(descriptors); + + EASY_GLOBALS.gui_blocks.clear(); + EASY_GLOBALS.gui_blocks.resize(nblocks); + memset(EASY_GLOBALS.gui_blocks.data(), 0, sizeof(::profiler_gui::EasyBlock) * nblocks); + for (decltype(nblocks) i = 0; i < nblocks; ++i) { + auto& guiblock = EASY_GLOBALS.gui_blocks[i]; + guiblock.tree = ::std::move(blocks[i]); +#ifdef EASY_TREE_WIDGET__USE_VECTOR + ::profiler_gui::set_max(guiblock.tree_item); +#endif + } + + static_cast(m_graphicsView->widget())->view()->setTree(EASY_GLOBALS.profiler_blocks); + +#if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 + static_cast(m_descTreeWidget->widget())->build(); +#endif + if (m_dialogDescTree != nullptr) + m_dialogDescTree->build(); + + m_saveAction->setEnabled(true); + m_deleteAction->setEnabled(true); + } + else + { + QMessageBox::warning(this, "Warning", QString("Can not read profiled blocks.\n\nReason:\n%1").arg(m_reader.getError()), QMessageBox::Close); + + if (m_reader.isFile()) + { + auto index = m_lastFiles.indexOf(m_reader.filename(), 0); + if (index >= 0) + { + // Remove unexisting file from list + m_lastFiles.removeAt(index); + auto action = m_loadActionMenu->actions().at(index); + m_loadActionMenu->removeAction(action); + delete action; + } + } + } + + m_reader.interrupt(); + + m_readerTimer.stop(); + m_progress->setValue(100); + //m_progress->hide(); + + if (EASY_GLOBALS.all_items_expanded_by_default) + { + onExpandAllClicked(true); + } + } + else + { + m_progress->setValue(m_reader.progress()); + } +} + +void EasyMainWindow::onFileReaderCancel() +{ + m_readerTimer.stop(); + m_reader.interrupt(); + m_progress->setValue(100); + //m_progress->hide(); +} + +////////////////////////////////////////////////////////////////////////// + +EasyFileReader::EasyFileReader() +{ + +} + +EasyFileReader::~EasyFileReader() +{ + interrupt(); +} + +const bool EasyFileReader::isFile() const +{ + return m_isFile; +} + +bool EasyFileReader::done() const +{ + return m_bDone.load(::std::memory_order_acquire); +} + +int EasyFileReader::progress() const +{ + return m_progress.load(::std::memory_order_acquire); +} + +unsigned int EasyFileReader::size() const +{ + return m_size.load(::std::memory_order_acquire); +} + +const QString& EasyFileReader::filename() const +{ + return m_filename; +} + +void EasyFileReader::load(const QString& _filename) +{ + interrupt(); + + m_isFile = true; + m_filename = _filename; + m_thread = ::std::thread([this](bool _enableStatistics) { + m_size.store(fillTreesFromFile(m_progress, m_filename.toStdString().c_str(), m_serializedBlocks, m_serializedDescriptors, + m_descriptors, m_blocks, m_blocksTree, m_descriptorsNumberInFile, _enableStatistics, m_errorMessage), ::std::memory_order_release); + m_progress.store(100, ::std::memory_order_release); + m_bDone.store(true, ::std::memory_order_release); + }, EASY_GLOBALS.enable_statistics); +} + +void EasyFileReader::load(::std::stringstream& _stream) +{ + interrupt(); + + m_isFile = false; + m_filename.clear(); + +#if defined(__GNUC__) && __GNUC__ < 5 && !defined(__llvm__) + // gcc 4 has a known bug which has been solved in gcc 5: + // std::stringstream has no swap() method :( + // have to copy all contents... Use gcc 5 or higher! +#pragma message "Warning: in gcc 4 and lower std::stringstream has no swap()! Memory consumption may increase! Better use gcc 5 or higher instead." + m_stream.str(_stream.str()); +#else + m_stream.swap(_stream); +#endif + + m_thread = ::std::thread([this](bool _enableStatistics) { + ::std::ofstream cache_file(NETWORK_CACHE_FILE, ::std::fstream::binary); + if (cache_file.is_open()) { + cache_file << m_stream.str(); + cache_file.close(); + } + m_size.store(fillTreesFromStream(m_progress, m_stream, m_serializedBlocks, m_serializedDescriptors, m_descriptors, + m_blocks, m_blocksTree, m_descriptorsNumberInFile, _enableStatistics, m_errorMessage), ::std::memory_order_release); + m_progress.store(100, ::std::memory_order_release); + m_bDone.store(true, ::std::memory_order_release); + }, EASY_GLOBALS.enable_statistics); +} + +void EasyFileReader::interrupt() +{ + m_progress.store(-100, ::std::memory_order_release); + if (m_thread.joinable()) + m_thread.join(); + + m_bDone.store(false, ::std::memory_order_release); + m_progress.store(0, ::std::memory_order_release); + m_size.store(0, ::std::memory_order_release); + m_serializedBlocks.clear(); + m_serializedDescriptors.clear(); + m_descriptors.clear(); + m_blocks.clear(); + m_blocksTree.clear(); + m_descriptorsNumberInFile = 0; + + clear_stream(m_stream); + clear_stream(m_errorMessage); +} + +void EasyFileReader::get(::profiler::SerializedData& _serializedBlocks, ::profiler::SerializedData& _serializedDescriptors, + ::profiler::descriptors_list_t& _descriptors, ::profiler::blocks_t& _blocks, + ::profiler::thread_blocks_tree_t& _tree, uint32_t& _descriptorsNumberInFile, QString& _filename) +{ + if (done()) + { + m_serializedBlocks.swap(_serializedBlocks); + m_serializedDescriptors.swap(_serializedDescriptors); + ::profiler::descriptors_list_t(::std::move(m_descriptors)).swap(_descriptors); + m_blocks.swap(_blocks); + m_blocksTree.swap(_tree); + m_filename.swap(_filename); + _descriptorsNumberInFile = m_descriptorsNumberInFile; + } +} + +QString EasyFileReader::getError() +{ + return QString(m_errorMessage.str().c_str()); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onEventTracingPriorityChange(bool _checked) +{ + if (EASY_GLOBALS.connected) + m_listener.send(profiler::net::BoolMessage(profiler::net::MESSAGE_TYPE_EVENT_TRACING_PRIORITY, _checked)); +} + +void EasyMainWindow::onEventTracingEnableChange(bool _checked) +{ + if (EASY_GLOBALS.connected) + m_listener.send(profiler::net::BoolMessage(profiler::net::MESSAGE_TYPE_EVENT_TRACING_STATUS, _checked)); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onFrameTimeEditFinish() +{ + auto text = m_frameTimeEdit->text(); + if (text.contains(QChar(','))) + { + text.remove(QChar('.')).replace(QChar(','), QChar('.')); + m_frameTimeEdit->setText(text); + } + + EASY_GLOBALS.frame_time = text.toFloat() * 1e3f; + emit EASY_GLOBALS.events.timelineMarkerChanged(); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onConnectClicked(bool) +{ + auto text = m_addressEdit->text(); +// auto parts = text.split(QChar('.')); +// if (parts.size() != 4) +// { +// QMessageBox::warning(this, "Warning", "Invalid IP-Address", QMessageBox::Close); +// +// if (EASY_GLOBALS.connected) +// { +// // Restore last values +// m_addressEdit->setText(m_lastAddress); +// m_portEdit->setText(QString::number(m_lastPort)); +// } +// +// return; +// } +// +// for (auto& part : parts) +// { +// int i = 0; +// for (; i < part.size(); ++i) +// { +// if (part[i] != QChar('0')) +// break; +// } +// +// if (i < part.size()) +// part = part.mid(i); +// else +// part = "0"; +// } + + QString& address = text;// parts.join(QChar('.')); + const decltype(m_lastPort) port = m_portEdit->text().toUShort(); + //m_addressEdit->setText(address); + + const bool isReconnecting = (EASY_GLOBALS.connected && m_listener.port() == port && address.toStdString() == m_listener.address()); + if (EASY_GLOBALS.connected) + { + if (QMessageBox::question(this, isReconnecting ? "Reconnect" : "New connection", QString("Current connection will be broken\n\n%1") + .arg(isReconnecting ? "Re-connect?" : "Establish new connection?"), + QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes) + { + if (!isReconnecting) + { + // Restore last values + m_addressEdit->setText(m_lastAddress); + m_portEdit->setText(QString::number(m_lastPort)); + } + + return; + } + } + + profiler::net::EasyProfilerStatus reply(false, false, false); + if (!m_listener.connect(address.toStdString().c_str(), port, reply)) + { + if (EASY_GLOBALS.connected && !isReconnecting) + { + if (QMessageBox::warning(this, "Warning", QString("Cannot connect to %1\n\nRestore previous connection?").arg(address), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) + { + if (!m_listener.connect(m_lastAddress.toStdString().c_str(), m_lastPort, reply)) + { + QMessageBox::warning(this, "Warning", "Cannot restore previous connection", QMessageBox::Close); + setDisconnected(false); + m_lastAddress = ::std::move(address); + m_lastPort = port; + } + else + { + m_addressEdit->setText(m_lastAddress); + m_portEdit->setText(QString::number(m_lastPort)); + // QMessageBox::information(this, "Information", "Previous connection restored", QMessageBox::Close); + } + } + else + { + setDisconnected(false); + m_lastAddress = ::std::move(address); + m_lastPort = port; + } + } + else + { + QMessageBox::warning(this, "Warning", QString("Cannot connect to %1").arg(address), QMessageBox::Close); + if (EASY_GLOBALS.connected) + setDisconnected(false); + + if (!isReconnecting) + { + m_lastAddress = ::std::move(address); + m_lastPort = port; + } + } + + return; + } + + m_lastAddress = ::std::move(address); + m_lastPort = port; + + qInfo() << "Connected successfully"; + EASY_GLOBALS.connected = true; + m_captureAction->setEnabled(true); + SET_ICON(m_connectAction, ":/Connection-on"); + + disconnect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange); + disconnect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange); + + m_eventTracingEnableAction->setEnabled(true); + m_eventTracingPriorityAction->setEnabled(true); + + m_eventTracingEnableAction->setChecked(reply.isEventTracingEnabled); + m_eventTracingPriorityAction->setChecked(reply.isLowPriorityEventTracing); + + connect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange); + connect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange); + + emit EASY_GLOBALS.events.connectionChanged(true); +} + +void EasyMainWindow::onCaptureClicked(bool) +{ + if (!EASY_GLOBALS.connected) + { + QMessageBox::warning(this, "Warning", "No connection with profiling app", QMessageBox::Close); + return; + } + + if (m_listener.regime() != LISTENER_IDLE) + { + if (m_listener.regime() == LISTENER_CAPTURE) + QMessageBox::warning(this, "Warning", "Already capturing frames.\nFinish old capturing session first.", QMessageBox::Close); + else + QMessageBox::warning(this, "Warning", "Capturing blocks description.\nFinish old capturing session first.", QMessageBox::Close); + return; + } + + if (!m_listener.startCapture()) + { + // Connection lost. Try to restore connection. + + profiler::net::EasyProfilerStatus reply(false, false, false); + if (!m_listener.connect(m_lastAddress.toStdString().c_str(), m_lastPort, reply)) + { + setDisconnected(); + return; + } + + if (!m_listener.startCapture()) + { + setDisconnected(); + return; + } + } + + m_listenerTimer.start(250); + + m_listenerDialog = new QMessageBox(QMessageBox::Information, "Capturing frames...", "Close this dialog to stop capturing.", QMessageBox::NoButton, this); + + auto button = new QToolButton(m_listenerDialog); + button->setAutoRaise(true); + button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + button->setIconSize(::profiler_gui::ICONS_SIZE); + button->setIcon(QIcon(":/Stop")); + button->setText("Stop"); + m_listenerDialog->addButton(button, QMessageBox::AcceptRole); + + m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true); + connect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose); + m_listenerDialog->show(); +} + +void EasyMainWindow::onGetBlockDescriptionsClicked(bool) +{ + if (!EASY_GLOBALS.connected) + { + QMessageBox::warning(this, "Warning", "No connection with profiling app", QMessageBox::Close); + return; + } + + if (m_listener.regime() != LISTENER_IDLE) + { + if (m_listener.regime() == LISTENER_DESCRIBE) + QMessageBox::warning(this, "Warning", "Already capturing blocks description.\nFinish old capturing session first.", QMessageBox::Close); + else + QMessageBox::warning(this, "Warning", "Capturing capturing frames.\nFinish old capturing session first.", QMessageBox::Close); + return; + } + + m_listenerDialog = new QMessageBox(QMessageBox::Information, "Waiting for blocks...", "This may take some time.", QMessageBox::NoButton, this); + m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true); + m_listenerDialog->show(); + + m_listener.requestBlocksDescription(); + + m_listenerDialog->reject(); + m_listenerDialog = nullptr; + + if (m_listener.size() != 0) + { + // Read descriptions from stream + + decltype(EASY_GLOBALS.descriptors) descriptors; + decltype(m_serializedDescriptors) serializedDescriptors; + ::std::stringstream errorMessage; + if (readDescriptionsFromStream(m_listener.data(), serializedDescriptors, descriptors, errorMessage)) + { + // Merge old and new descriptions + + bool cancel = false; + const bool doFlush = m_descriptorsNumberInFile > descriptors.size(); + if (doFlush && !m_serializedBlocks.empty()) + { + auto button = QMessageBox::question(this, "Information", + QString("New blocks description number = %1\nis less than the old one = %2.\nTo avoid possible conflicts\nall profiled data will be deleted.\nContinue?") + .arg(descriptors.size()) + .arg(m_descriptorsNumberInFile), + QMessageBox::Yes, QMessageBox::No); + + if (button == QMessageBox::Yes) + clear(); // Clear all contents because new descriptors list conflicts with old one + else + cancel = true; + } + + if (!cancel) + { + if (!doFlush && m_descriptorsNumberInFile < EASY_GLOBALS.descriptors.size()) + { + // There are dynamically added descriptors, add them to the new list too + + auto newnumber = static_cast(descriptors.size()); + auto size = static_cast(EASY_GLOBALS.descriptors.size()); + auto diff = newnumber - size; + decltype(newnumber) failnumber = 0; + + descriptors.reserve(descriptors.size() + EASY_GLOBALS.descriptors.size() - m_descriptorsNumberInFile); + for (auto i = m_descriptorsNumberInFile; i < size; ++i) + { + auto id = EASY_GLOBALS.descriptors[i]->id(); + if (id < newnumber) + descriptors.push_back(descriptors[id]); + else + ++failnumber; + } + + if (failnumber != 0) + { + // There are some errors... + + // revert changes + descriptors.resize(newnumber); + + // clear all profiled data to avoid conflicts + auto button = QMessageBox::question(this, "Information", + "There are errors while merging block descriptions lists.\nTo avoid possible conflicts\nall profiled data will be deleted.\nContinue?", + QMessageBox::Yes, QMessageBox::No); + + if (button == QMessageBox::Yes) + clear(); // Clear all contents because new descriptors list conflicts with old one + else + cancel = true; + } + + if (!cancel && diff != 0) + { + for (auto& b : EASY_GLOBALS.gui_blocks) + { + if (b.tree.node->id() >= m_descriptorsNumberInFile) + b.tree.node->setId(b.tree.node->id() + diff); + } + + m_descriptorsNumberInFile = newnumber; + } + } + + if (!cancel) + { + EASY_GLOBALS.descriptors.swap(descriptors); + m_serializedDescriptors.swap(serializedDescriptors); + m_descriptorsNumberInFile = static_cast(EASY_GLOBALS.descriptors.size()); + + if (m_descTreeDialog != nullptr) + { +#if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0 + static_cast(m_descTreeWidget->widget())->build(); +#endif + m_dialogDescTree->build(); + m_descTreeDialog->raise(); + } + else + { + onEditBlocksClicked(true); + } + } + } + } + else + { + QMessageBox::warning(this, "Warning", QString("Can not read blocks description from stream.\n\nReason:\n%1").arg(errorMessage.str().c_str()), QMessageBox::Close); + } + + m_listener.clearData(); + } + + if (!m_listener.connected()) + { + setDisconnected(); + } +} + +////////////////////////////////////////////////////////////////////////// + +void EasyMainWindow::onBlockStatusChange(::profiler::block_id_t _id, ::profiler::EasyBlockStatus _status) +{ + if (EASY_GLOBALS.connected) + m_listener.send(profiler::net::BlockStatusMessage(_id, static_cast(_status))); +} + +////////////////////////////////////////////////////////////////////////// + +EasySocketListener::EasySocketListener() : m_receivedSize(0), m_port(0), m_regime(LISTENER_IDLE) +{ + m_bInterrupt = ATOMIC_VAR_INIT(false); + m_bConnected = ATOMIC_VAR_INIT(false); + m_bStopReceive = ATOMIC_VAR_INIT(false); +} + +EasySocketListener::~EasySocketListener() +{ + m_bInterrupt.store(true, ::std::memory_order_release); + if (m_thread.joinable()) + m_thread.join(); +} + +bool EasySocketListener::connected() const +{ + return m_bConnected.load(::std::memory_order_acquire); +} + +EasyListenerRegime EasySocketListener::regime() const +{ + return m_regime; +} + +uint64_t EasySocketListener::size() const +{ + return m_receivedSize; +} + +::std::stringstream& EasySocketListener::data() +{ + return m_receivedData; +} + const ::std::string& EasySocketListener::address() const { return m_address; } -uint16_t EasySocketListener::port() const -{ - return m_port; -} - -void EasySocketListener::clearData() -{ - clear_stream(m_receivedData); - m_receivedSize = 0; -} - -bool EasySocketListener::connect(const char* _ipaddress, uint16_t _port, profiler::net::EasyProfilerStatus& _reply) -{ - if (connected()) - { - m_bInterrupt.store(true, ::std::memory_order_release); - if (m_thread.joinable()) - m_thread.join(); - - m_bConnected.store(false, ::std::memory_order_release); - m_bInterrupt.store(false, ::std::memory_order_release); - } - - m_address.clear(); - m_port = 0; - - m_easySocket.flush(); - m_easySocket.init(); - int res = m_easySocket.setAddress(_ipaddress, _port); - res = m_easySocket.connect(); - - const bool isConnected = res == 0; - if (isConnected) - { - static const size_t buffer_size = sizeof(profiler::net::EasyProfilerStatus) << 1; - char buffer[buffer_size] = {}; - int bytes = 0; - - while (true) - { - bytes = m_easySocket.receive(buffer, buffer_size); - - if (bytes == -1) - { - if (m_easySocket.isDisconnected()) - return false; - bytes = 0; - continue; - } - - break; - } - - if (bytes == 0) - { - m_address = _ipaddress; - m_port = _port; - m_bConnected.store(isConnected, ::std::memory_order_release); - return isConnected; - } - - size_t seek = bytes; - while (seek < sizeof(profiler::net::EasyProfilerStatus)) - { - bytes = m_easySocket.receive(buffer + seek, buffer_size - seek); - - if (bytes == -1) - { - if (m_easySocket.isDisconnected()) - return false; - break; - } - - seek += bytes; - } - - auto message = reinterpret_cast(buffer); - if (message->isEasyNetMessage() && message->type == profiler::net::MESSAGE_TYPE_ACCEPTED_CONNECTION) - _reply = *message; - - m_address = _ipaddress; - m_port = _port; - } - - m_bConnected.store(isConnected, ::std::memory_order_release); - return isConnected; -} - -bool EasySocketListener::startCapture() -{ - clearData(); - - profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_START_CAPTURE); - m_easySocket.send(&request, sizeof(request)); - - if (m_easySocket.isDisconnected()) { - m_bConnected.store(false, ::std::memory_order_release); - return false; - } - - m_regime = LISTENER_CAPTURE; - m_thread = ::std::thread(&EasySocketListener::listenCapture, this); - - return true; -} - -void EasySocketListener::stopCapture() -{ - if (!m_thread.joinable() || m_regime != LISTENER_CAPTURE) - return; - - m_bStopReceive.store(true, ::std::memory_order_release); - - //profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_STOP_CAPTURE); - //m_easySocket.send(&request, sizeof(request)); - - m_thread.join(); - - if (m_easySocket.isDisconnected()) - m_bConnected.store(false, ::std::memory_order_release); - - m_regime = LISTENER_IDLE; - m_bStopReceive.store(false, ::std::memory_order_release); -} - -void EasySocketListener::requestBlocksDescription() -{ - clearData(); - - profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_BLOCKS_DESCRIPTION); - m_easySocket.send(&request, sizeof(request)); - - if(m_easySocket.isDisconnected() ){ - m_bConnected.store(false, ::std::memory_order_release); - } - - m_regime = LISTENER_DESCRIBE; - listenDescription(); - m_regime = LISTENER_IDLE; -} - -////////////////////////////////////////////////////////////////////////// - -void EasySocketListener::listenCapture() -{ - // TODO: Merge functions listenCapture() and listenDescription() - - static const int buffer_size = 8 * 1024 * 1024; - char* buffer = new char[buffer_size]; - int seek = 0, bytes = 0; - auto timeBegin = ::std::chrono::system_clock::now(); - - bool isListen = true, disconnected = false; - while (isListen && !m_bInterrupt.load(::std::memory_order_acquire)) - { - if (m_bStopReceive.load(::std::memory_order_acquire)) - { - profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_STOP_CAPTURE); - m_easySocket.send(&request, sizeof(request)); - m_bStopReceive.store(false, ::std::memory_order_release); - } - - if ((bytes - seek) == 0) - { - bytes = m_easySocket.receive(buffer, buffer_size); - - if (bytes == -1) - { - if (m_easySocket.isDisconnected()) - { - m_bConnected.store(false, ::std::memory_order_release); - isListen = false; - disconnected = true; - } - - seek = 0; - bytes = 0; - - continue; - } - - seek = 0; - } - - if (bytes == 0) - { - isListen = false; - break; - } - - char* buf = buffer + seek; - - if (bytes > 0) - { - auto message = reinterpret_cast(buf); - if (!message->isEasyNetMessage()) - continue; - - switch (message->type) - { - case profiler::net::MESSAGE_TYPE_ACCEPTED_CONNECTION: - { - qInfo() << "Receive MESSAGE_TYPE_ACCEPTED_CONNECTION"; - //m_easySocket.send(&request, sizeof(request)); - seek += sizeof(profiler::net::Message); - break; - } - - case profiler::net::MESSAGE_TYPE_REPLY_START_CAPTURING: - { - qInfo() << "Receive MESSAGE_TYPE_REPLY_START_CAPTURING"; - seek += sizeof(profiler::net::Message); - break; - } - - case profiler::net::MESSAGE_TYPE_REPLY_BLOCKS_END: - { - qInfo() << "Receive MESSAGE_TYPE_REPLY_BLOCKS_END"; - seek += sizeof(profiler::net::Message); - - const auto dt = ::std::chrono::duration_cast(::std::chrono::system_clock::now() - timeBegin); - const auto bytesNumber = m_receivedData.str().size(); - qInfo() << "recieved " << bytesNumber << " bytes, " << dt.count() << " ms, average speed = " << double(bytesNumber) * 1e3 / double(dt.count()) / 1024. << " kBytes/sec"; - - seek = 0; - bytes = 0; - - isListen = false; - - break; - } - - case profiler::net::MESSAGE_TYPE_REPLY_BLOCKS: - { - qInfo() << "Receive MESSAGE_TYPE_REPLY_BLOCKS"; - - seek += sizeof(profiler::net::DataMessage); - profiler::net::DataMessage* dm = (profiler::net::DataMessage*)message; - timeBegin = std::chrono::system_clock::now(); - - int neededSize = dm->size; - - - buf = buffer + seek; - auto bytesNumber = ::std::min((int)dm->size, bytes - seek); - m_receivedSize += bytesNumber; - m_receivedData.write(buf, bytesNumber); - neededSize -= bytesNumber; - - if (neededSize == 0) - seek += bytesNumber; - else - { - seek = 0; - bytes = 0; - } - - - int loaded = 0; - while (neededSize > 0) - { - bytes = m_easySocket.receive(buffer, buffer_size); - - if (bytes == -1) - { - if (m_easySocket.isDisconnected()) - { - m_bConnected.store(false, ::std::memory_order_release); - isListen = false; - disconnected = true; - neededSize = 0; - } - - break; - } - - buf = buffer; - int toWrite = ::std::min(bytes, neededSize); - m_receivedSize += toWrite; - m_receivedData.write(buf, toWrite); - neededSize -= toWrite; - loaded += toWrite; - seek = toWrite; - } - - if (m_bStopReceive.load(::std::memory_order_acquire)) - { - profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_STOP_CAPTURE); - m_easySocket.send(&request, sizeof(request)); - m_bStopReceive.store(false, ::std::memory_order_release); - } - - break; - } - - default: - //qInfo() << "Receive unknown " << message->type; - break; - } - } - } - - if (disconnected) - clearData(); - - delete [] buffer; -} - -void EasySocketListener::listenDescription() -{ - // TODO: Merge functions listenDescription() and listenCapture() - - static const int buffer_size = 8 * 1024 * 1024; - char* buffer = new char[buffer_size]; - int seek = 0, bytes = 0; - - bool isListen = true, disconnected = false; - while (isListen && !m_bInterrupt.load(::std::memory_order_acquire)) - { - if ((bytes - seek) == 0) - { - bytes = m_easySocket.receive(buffer, buffer_size); - - if (bytes == -1) - { - if (m_easySocket.isDisconnected()) - { - m_bConnected.store(false, ::std::memory_order_release); - isListen = false; - disconnected = true; - } - - seek = 0; - bytes = 0; - - continue; - } - - seek = 0; - } - - if (bytes == 0) - { - isListen = false; - break; - } - - char* buf = buffer + seek; - - if (bytes > 0) - { - auto message = reinterpret_cast(buf); - if (!message->isEasyNetMessage()) - continue; - - switch (message->type) - { - case profiler::net::MESSAGE_TYPE_ACCEPTED_CONNECTION: - { - qInfo() << "Receive MESSAGE_TYPE_ACCEPTED_CONNECTION"; - seek += sizeof(profiler::net::Message); - break; - } - - case profiler::net::MESSAGE_TYPE_REPLY_BLOCKS_DESCRIPTION_END: - { - qInfo() << "Receive MESSAGE_TYPE_REPLY_BLOCKS_DESCRIPTION_END"; - seek += sizeof(profiler::net::Message); - - seek = 0; - bytes = 0; - - isListen = false; - - break; - } - - case profiler::net::MESSAGE_TYPE_REPLY_BLOCKS_DESCRIPTION: - { - qInfo() << "Receive MESSAGE_TYPE_REPLY_BLOCKS"; - - seek += sizeof(profiler::net::DataMessage); - profiler::net::DataMessage* dm = (profiler::net::DataMessage*)message; - int neededSize = dm->size; - - buf = buffer + seek; - auto bytesNumber = ::std::min((int)dm->size, bytes - seek); - m_receivedSize += bytesNumber; - m_receivedData.write(buf, bytesNumber); - neededSize -= bytesNumber; - - if (neededSize == 0) - seek += bytesNumber; - else{ - seek = 0; - bytes = 0; - } - - int loaded = 0; - while (neededSize > 0) - { - bytes = m_easySocket.receive(buffer, buffer_size); - - if (bytes == -1) - { - if (m_easySocket.isDisconnected()) - { - m_bConnected.store(false, ::std::memory_order_release); - isListen = false; - disconnected = true; - neededSize = 0; - } - - break; - } - - buf = buffer; - int toWrite = ::std::min(bytes, neededSize); - m_receivedSize += toWrite; - m_receivedData.write(buf, toWrite); - neededSize -= toWrite; - loaded += toWrite; - seek = toWrite; - } - - break; - } - - default: - break; - } - } - } - - if (disconnected) - clearData(); - - delete[] buffer; -} - -////////////////////////////////////////////////////////////////////////// - +uint16_t EasySocketListener::port() const +{ + return m_port; +} + +void EasySocketListener::clearData() +{ + clear_stream(m_receivedData); + m_receivedSize = 0; +} + +bool EasySocketListener::connect(const char* _ipaddress, uint16_t _port, profiler::net::EasyProfilerStatus& _reply) +{ + if (connected()) + { + m_bInterrupt.store(true, ::std::memory_order_release); + if (m_thread.joinable()) + m_thread.join(); + + m_bConnected.store(false, ::std::memory_order_release); + m_bInterrupt.store(false, ::std::memory_order_release); + } + + m_address.clear(); + m_port = 0; + + m_easySocket.flush(); + m_easySocket.init(); + int res = m_easySocket.setAddress(_ipaddress, _port); + res = m_easySocket.connect(); + + const bool isConnected = res == 0; + if (isConnected) + { + static const size_t buffer_size = sizeof(profiler::net::EasyProfilerStatus) << 1; + char buffer[buffer_size] = {}; + int bytes = 0; + + while (true) + { + bytes = m_easySocket.receive(buffer, buffer_size); + + if (bytes == -1) + { + if (m_easySocket.isDisconnected()) + return false; + bytes = 0; + continue; + } + + break; + } + + if (bytes == 0) + { + m_address = _ipaddress; + m_port = _port; + m_bConnected.store(isConnected, ::std::memory_order_release); + return isConnected; + } + + size_t seek = bytes; + while (seek < sizeof(profiler::net::EasyProfilerStatus)) + { + bytes = m_easySocket.receive(buffer + seek, buffer_size - seek); + + if (bytes == -1) + { + if (m_easySocket.isDisconnected()) + return false; + break; + } + + seek += bytes; + } + + auto message = reinterpret_cast(buffer); + if (message->isEasyNetMessage() && message->type == profiler::net::MESSAGE_TYPE_ACCEPTED_CONNECTION) + _reply = *message; + + m_address = _ipaddress; + m_port = _port; + } + + m_bConnected.store(isConnected, ::std::memory_order_release); + return isConnected; +} + +bool EasySocketListener::startCapture() +{ + clearData(); + + profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_START_CAPTURE); + m_easySocket.send(&request, sizeof(request)); + + if (m_easySocket.isDisconnected()) { + m_bConnected.store(false, ::std::memory_order_release); + return false; + } + + m_regime = LISTENER_CAPTURE; + m_thread = ::std::thread(&EasySocketListener::listenCapture, this); + + return true; +} + +void EasySocketListener::stopCapture() +{ + if (!m_thread.joinable() || m_regime != LISTENER_CAPTURE) + return; + + m_bStopReceive.store(true, ::std::memory_order_release); + + //profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_STOP_CAPTURE); + //m_easySocket.send(&request, sizeof(request)); + + m_thread.join(); + + if (m_easySocket.isDisconnected()) + m_bConnected.store(false, ::std::memory_order_release); + + m_regime = LISTENER_IDLE; + m_bStopReceive.store(false, ::std::memory_order_release); +} + +void EasySocketListener::requestBlocksDescription() +{ + clearData(); + + profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_BLOCKS_DESCRIPTION); + m_easySocket.send(&request, sizeof(request)); + + if(m_easySocket.isDisconnected() ){ + m_bConnected.store(false, ::std::memory_order_release); + } + + m_regime = LISTENER_DESCRIBE; + listenDescription(); + m_regime = LISTENER_IDLE; +} + +////////////////////////////////////////////////////////////////////////// + +void EasySocketListener::listenCapture() +{ + // TODO: Merge functions listenCapture() and listenDescription() + + static const int buffer_size = 8 * 1024 * 1024; + char* buffer = new char[buffer_size]; + int seek = 0, bytes = 0; + auto timeBegin = ::std::chrono::system_clock::now(); + + bool isListen = true, disconnected = false; + while (isListen && !m_bInterrupt.load(::std::memory_order_acquire)) + { + if (m_bStopReceive.load(::std::memory_order_acquire)) + { + profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_STOP_CAPTURE); + m_easySocket.send(&request, sizeof(request)); + m_bStopReceive.store(false, ::std::memory_order_release); + } + + if ((bytes - seek) == 0) + { + bytes = m_easySocket.receive(buffer, buffer_size); + + if (bytes == -1) + { + if (m_easySocket.isDisconnected()) + { + m_bConnected.store(false, ::std::memory_order_release); + isListen = false; + disconnected = true; + } + + seek = 0; + bytes = 0; + + continue; + } + + seek = 0; + } + + if (bytes == 0) + { + isListen = false; + break; + } + + char* buf = buffer + seek; + + if (bytes > 0) + { + auto message = reinterpret_cast(buf); + if (!message->isEasyNetMessage()) + continue; + + switch (message->type) + { + case profiler::net::MESSAGE_TYPE_ACCEPTED_CONNECTION: + { + qInfo() << "Receive MESSAGE_TYPE_ACCEPTED_CONNECTION"; + //m_easySocket.send(&request, sizeof(request)); + seek += sizeof(profiler::net::Message); + break; + } + + case profiler::net::MESSAGE_TYPE_REPLY_START_CAPTURING: + { + qInfo() << "Receive MESSAGE_TYPE_REPLY_START_CAPTURING"; + seek += sizeof(profiler::net::Message); + break; + } + + case profiler::net::MESSAGE_TYPE_REPLY_BLOCKS_END: + { + qInfo() << "Receive MESSAGE_TYPE_REPLY_BLOCKS_END"; + seek += sizeof(profiler::net::Message); + + const auto dt = ::std::chrono::duration_cast(::std::chrono::system_clock::now() - timeBegin); + const auto bytesNumber = m_receivedData.str().size(); + qInfo() << "recieved " << bytesNumber << " bytes, " << dt.count() << " ms, average speed = " << double(bytesNumber) * 1e3 / double(dt.count()) / 1024. << " kBytes/sec"; + + seek = 0; + bytes = 0; + + isListen = false; + + break; + } + + case profiler::net::MESSAGE_TYPE_REPLY_BLOCKS: + { + qInfo() << "Receive MESSAGE_TYPE_REPLY_BLOCKS"; + + seek += sizeof(profiler::net::DataMessage); + profiler::net::DataMessage* dm = (profiler::net::DataMessage*)message; + timeBegin = std::chrono::system_clock::now(); + + int neededSize = dm->size; + + + buf = buffer + seek; + auto bytesNumber = ::std::min((int)dm->size, bytes - seek); + m_receivedSize += bytesNumber; + m_receivedData.write(buf, bytesNumber); + neededSize -= bytesNumber; + + if (neededSize == 0) + seek += bytesNumber; + else + { + seek = 0; + bytes = 0; + } + + + int loaded = 0; + while (neededSize > 0) + { + bytes = m_easySocket.receive(buffer, buffer_size); + + if (bytes == -1) + { + if (m_easySocket.isDisconnected()) + { + m_bConnected.store(false, ::std::memory_order_release); + isListen = false; + disconnected = true; + neededSize = 0; + } + + break; + } + + buf = buffer; + int toWrite = ::std::min(bytes, neededSize); + m_receivedSize += toWrite; + m_receivedData.write(buf, toWrite); + neededSize -= toWrite; + loaded += toWrite; + seek = toWrite; + } + + if (m_bStopReceive.load(::std::memory_order_acquire)) + { + profiler::net::Message request(profiler::net::MESSAGE_TYPE_REQUEST_STOP_CAPTURE); + m_easySocket.send(&request, sizeof(request)); + m_bStopReceive.store(false, ::std::memory_order_release); + } + + break; + } + + default: + //qInfo() << "Receive unknown " << message->type; + break; + } + } + } + + if (disconnected) + clearData(); + + delete [] buffer; +} + +void EasySocketListener::listenDescription() +{ + // TODO: Merge functions listenDescription() and listenCapture() + + static const int buffer_size = 8 * 1024 * 1024; + char* buffer = new char[buffer_size]; + int seek = 0, bytes = 0; + + bool isListen = true, disconnected = false; + while (isListen && !m_bInterrupt.load(::std::memory_order_acquire)) + { + if ((bytes - seek) == 0) + { + bytes = m_easySocket.receive(buffer, buffer_size); + + if (bytes == -1) + { + if (m_easySocket.isDisconnected()) + { + m_bConnected.store(false, ::std::memory_order_release); + isListen = false; + disconnected = true; + } + + seek = 0; + bytes = 0; + + continue; + } + + seek = 0; + } + + if (bytes == 0) + { + isListen = false; + break; + } + + char* buf = buffer + seek; + + if (bytes > 0) + { + auto message = reinterpret_cast(buf); + if (!message->isEasyNetMessage()) + continue; + + switch (message->type) + { + case profiler::net::MESSAGE_TYPE_ACCEPTED_CONNECTION: + { + qInfo() << "Receive MESSAGE_TYPE_ACCEPTED_CONNECTION"; + seek += sizeof(profiler::net::Message); + break; + } + + case profiler::net::MESSAGE_TYPE_REPLY_BLOCKS_DESCRIPTION_END: + { + qInfo() << "Receive MESSAGE_TYPE_REPLY_BLOCKS_DESCRIPTION_END"; + seek += sizeof(profiler::net::Message); + + seek = 0; + bytes = 0; + + isListen = false; + + break; + } + + case profiler::net::MESSAGE_TYPE_REPLY_BLOCKS_DESCRIPTION: + { + qInfo() << "Receive MESSAGE_TYPE_REPLY_BLOCKS"; + + seek += sizeof(profiler::net::DataMessage); + profiler::net::DataMessage* dm = (profiler::net::DataMessage*)message; + int neededSize = dm->size; + + buf = buffer + seek; + auto bytesNumber = ::std::min((int)dm->size, bytes - seek); + m_receivedSize += bytesNumber; + m_receivedData.write(buf, bytesNumber); + neededSize -= bytesNumber; + + if (neededSize == 0) + seek += bytesNumber; + else{ + seek = 0; + bytes = 0; + } + + int loaded = 0; + while (neededSize > 0) + { + bytes = m_easySocket.receive(buffer, buffer_size); + + if (bytes == -1) + { + if (m_easySocket.isDisconnected()) + { + m_bConnected.store(false, ::std::memory_order_release); + isListen = false; + disconnected = true; + neededSize = 0; + } + + break; + } + + buf = buffer; + int toWrite = ::std::min(bytes, neededSize); + m_receivedSize += toWrite; + m_receivedData.write(buf, toWrite); + neededSize -= toWrite; + loaded += toWrite; + seek = toWrite; + } + + break; + } + + default: + break; + } + } + } + + if (disconnected) + clearData(); + + delete[] buffer; +} + +////////////////////////////////////////////////////////////////////////// + diff --git a/profiler_gui/resources.qrc b/profiler_gui/resources.qrc index 1b5c988..ece37d3 100644 --- a/profiler_gui/resources.qrc +++ b/profiler_gui/resources.qrc @@ -1,29 +1,29 @@ - - - icons/logo.svg - icons/off.svg - icons/open-folder2.svg - icons/reload-folder2.svg - icons/reload.svg - icons/expand.svg - icons/collapse.svg - icons/colors.svg - icons/colors-black.svg - icons/save.svg - icons/statistics.svg - icons/statistics2.svg - icons/lan.svg - icons/lan_on.svg - icons/wifi.svg - icons/wifi_on.svg - icons/lan.svg - icons/lan_on.svg - icons/play.svg - icons/stop.svg - icons/delete.svg - icons/list.svg - icons/search-next.svg - icons/search-prev.svg - icons/settings.svg - - + + + icons/logo.svg + icons/off.svg + icons/open-folder2.svg + icons/reload-folder2.svg + icons/reload.svg + icons/expand.svg + icons/collapse.svg + icons/colors.svg + icons/colors-black.svg + icons/save.svg + icons/statistics.svg + icons/statistics2.svg + icons/lan.svg + icons/lan_on.svg + icons/wifi.svg + icons/wifi_on.svg + icons/lan.svg + icons/lan_on.svg + icons/play.svg + icons/stop.svg + icons/delete.svg + icons/list.svg + icons/search-next.svg + icons/search-prev.svg + icons/settings.svg + + diff --git a/profiler_gui/tree_widget_loader.cpp b/profiler_gui/tree_widget_loader.cpp index deb2852..774db24 100644 --- a/profiler_gui/tree_widget_loader.cpp +++ b/profiler_gui/tree_widget_loader.cpp @@ -1,317 +1,317 @@ -/************************************************************************ -* file name : tree_widget_loader.h -* ----------------- : -* creation time : 2016/08/18 -* author : Victor Zarubkin -* email : v.s.zarubkin@gmail.com -* ----------------- : -* description : The file contains implementation of EasyTreeWidgetLoader which aim is -* : to load EasyProfiler blocks hierarchy in separate thread. -* ----------------- : -* change log : * 2016/08/18 Victor Zarubkin: moved sources from blocks_tree_widget.h/.cpp -* : and renamed Prof* to Easy*. -* : -* : * -* ----------------- : -* license : Lightweight profiler library for c++ -* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin -* : -* : -* : Licensed under 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. -* : -* : -* : GNU General Public License Usage -* : Alternatively, this file may be used 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 . -************************************************************************/ - -#include "tree_widget_loader.h" -#include "tree_widget_item.h" -#include "globals.h" - -#ifdef _WIN32 -#include -#endif - -#ifdef max -#undef max -#endif - -#ifdef min -#undef min -#endif - -////////////////////////////////////////////////////////////////////////// - -EasyTreeWidgetLoader::EasyTreeWidgetLoader() - : m_bDone(ATOMIC_VAR_INIT(false)) - , m_bInterrupt(ATOMIC_VAR_INIT(false)) - , m_progress(ATOMIC_VAR_INIT(0)) - , m_mode(EasyTreeMode_Full) -{ -} - -EasyTreeWidgetLoader::~EasyTreeWidgetLoader() -{ - interrupt(true); -} - -bool EasyTreeWidgetLoader::done() const -{ - return m_bDone.load(); -} - -void EasyTreeWidgetLoader::setDone() -{ - m_bDone.store(true); - //m_progress.store(100); -} - -void EasyTreeWidgetLoader::setProgress(int _progress) -{ - m_progress.store(_progress); -} - -bool EasyTreeWidgetLoader::interrupted() const -{ - return m_bInterrupt.load(); -} - -int EasyTreeWidgetLoader::progress() const -{ - return m_progress.load(); -} - -void EasyTreeWidgetLoader::takeTopLevelItems(ThreadedItems& _output) -{ - if (done()) - { - _output = ::std::move(m_topLevelItems); - m_topLevelItems.clear(); - } -} - -void EasyTreeWidgetLoader::takeItems(Items& _output) -{ - if (done()) - { - _output = ::std::move(m_items); - m_items.clear(); - } -} - -void EasyTreeWidgetLoader::interrupt(bool _wait) -{ - m_bInterrupt.store(true); - if (m_thread.joinable()) - m_thread.join(); - - m_bInterrupt.store(false); - m_bDone.store(false); - m_progress.store(0); - - if (!_wait) - { - auto deleter_thread = ::std::thread([](decltype(m_topLevelItems) _items) { - for (auto item : _items) - delete item.second; - }, ::std::move(m_topLevelItems)); - -#ifdef _WIN32 - SetThreadPriority(deleter_thread.native_handle(), THREAD_PRIORITY_LOWEST); -#endif - - deleter_thread.detach(); - } - else - { - for (auto item : m_topLevelItems) - delete item.second; - } - - m_items.clear(); - m_topLevelItems.clear(); - m_iditems.clear(); -} - -void EasyTreeWidgetLoader::fillTree(::profiler::timestamp_t& _beginTime, const unsigned int _blocksNumber, const ::profiler::thread_blocks_tree_t& _blocksTree, bool _colorizeRows, EasyTreeMode _mode) -{ - interrupt(); - m_mode = _mode; - m_thread = ::std::thread(&EasyTreeWidgetLoader::setTreeInternal1, this, - ::std::ref(_beginTime), _blocksNumber, ::std::ref(_blocksTree), _colorizeRows, - EASY_GLOBALS.add_zero_blocks_to_hierarchy, EASY_GLOBALS.use_decorated_thread_name, EASY_GLOBALS.time_units); -} - -void EasyTreeWidgetLoader::fillTreeBlocks(const::profiler_gui::TreeBlocks& _blocks, ::profiler::timestamp_t _beginTime, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict, bool _colorizeRows, EasyTreeMode _mode) -{ - interrupt(); - m_mode = _mode; - m_thread = ::std::thread(&EasyTreeWidgetLoader::setTreeInternal2, this, - _beginTime, ::std::ref(_blocks), _left, _right, _strict, _colorizeRows, - EASY_GLOBALS.add_zero_blocks_to_hierarchy, EASY_GLOBALS.use_decorated_thread_name, EASY_GLOBALS.time_units); -} - -////////////////////////////////////////////////////////////////////////// - -void EasyTreeWidgetLoader::setTreeInternal1(::profiler::timestamp_t& _beginTime, const unsigned int _blocksNumber, const ::profiler::thread_blocks_tree_t& _blocksTree, bool _colorizeRows, bool _addZeroBlocks, bool _decoratedThreadNames, ::profiler_gui::TimeUnits _units) -{ - m_items.reserve(_blocksNumber + _blocksTree.size()); // _blocksNumber does not include Thread root blocks - - ::profiler::timestamp_t finishtime = 0; - for (const auto& threadTree : _blocksTree) - { - const auto node_block = blocksTree(threadTree.second.children.front()).node; - const auto startTime = node_block->begin(); - const auto endTime = node_block->end(); - - if (_beginTime > startTime) - _beginTime = startTime; - - if (finishtime < endTime) - finishtime = endTime; - } - - //const QSignalBlocker b(this); - const auto u_thread = ::profiler_gui::toUnicode("thread"); - int i = 0; - const int total = static_cast(_blocksTree.size()); - for (const auto& threadTree : _blocksTree) - { - if (interrupted()) - break; - - const auto& root = threadTree.second; - auto item = new EasyTreeWidgetItem(); - item->setText(COL_NAME, ::profiler_gui::decoratedThreadName(_decoratedThreadNames, root, u_thread)); - - ::profiler::timestamp_t duration = 0; - if (!root.children.empty()) - duration = blocksTree(root.children.back()).node->end() - blocksTree(root.children.front()).node->begin(); - - item->setTimeSmart(COL_DURATION, _units, duration); - item->setBackgroundColor(::profiler_gui::SELECTED_THREAD_BACKGROUND); - item->setTextColor(::profiler_gui::SELECTED_THREAD_FOREGROUND); - - //_items.push_back(item); - - item->setTimeSmart(COL_SELF_DURATION, _units, root.profiled_time); - - ::profiler::timestamp_t children_duration = 0; - const auto children_items_number = setTreeInternal(root, 0, _beginTime, root.children, item, nullptr, _beginTime, finishtime + 1000000000ULL, false, children_duration, _colorizeRows, _addZeroBlocks, _units); - - if (children_items_number > 0) - { - //total_items += children_items_number + 1; - //addTopLevelItem(item); - //m_roots[threadTree.first] = item; - m_topLevelItems.emplace_back(root.thread_id, item); - } - else - { - //_items.pop_back(); - delete item; - } - - setProgress((100 * ++i) / total); - } - - setDone(); - //return total_items; -} - -////////////////////////////////////////////////////////////////////////// - -// auto calculateTotalChildrenNumber(const ::profiler::BlocksTree& _tree) -> decltype(_tree.children.size()) -// { -// auto children_number = _tree.children.size(); -// for (auto i : _tree.children) -// children_number += calculateTotalChildrenNumber(blocksTree(i)); -// return children_number; -// } - -typedef ::std::unordered_map<::profiler::thread_id_t, ::profiler::block_index_t, ::profiler_gui::do_no_hash<::profiler::thread_id_t>::hasher_t> BeginEndIndicesMap; - -void EasyTreeWidgetLoader::setTreeInternal2(const ::profiler::timestamp_t& _beginTime, const ::profiler_gui::TreeBlocks& _blocks, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict, bool _colorizeRows, bool _addZeroBlocks, bool _decoratedThreadNames, ::profiler_gui::TimeUnits _units) -{ - //size_t blocksNumber = 0; - //for (const auto& block : _blocks) - // blocksNumber += calculateTotalChildrenNumber(*block.tree); - // //blocksNumber += block.tree->total_children_number; - //m_items.reserve(blocksNumber + _blocks.size()); // blocksNumber does not include root blocks - - BeginEndIndicesMap beginEndMap; - RootsMap threadsMap; - - auto const setTree = (m_mode == EasyTreeMode_Full) ? &EasyTreeWidgetLoader::setTreeInternal : &EasyTreeWidgetLoader::setTreeInternalPlain; - - const auto u_thread = ::profiler_gui::toUnicode("thread"); - int i = 0, total = static_cast(_blocks.size()); - //const QSignalBlocker b(this); - for (const auto& block : _blocks) - { - if (interrupted()) - break; - - auto& gui_block = easyBlock(block.tree); - const auto startTime = gui_block.tree.node->begin(); - const auto endTime = gui_block.tree.node->end(); - if (startTime > _right || endTime < _left) - { - setProgress((90 * ++i) / total); - continue; - } - - ::profiler::timestamp_t duration = 0; - EasyTreeWidgetItem* thread_item = nullptr; - ::profiler::block_index_t& firstCswitch = beginEndMap[block.root->thread_id]; - auto thread_item_it = threadsMap.find(block.root->thread_id); - if (thread_item_it != threadsMap.end()) - { - thread_item = thread_item_it->second; - } - else - { - thread_item = new EasyTreeWidgetItem(); - thread_item->setText(COL_NAME, ::profiler_gui::decoratedThreadName(_decoratedThreadNames, *block.root, u_thread)); - - if (!block.root->children.empty()) - duration = blocksTree(block.root->children.back()).node->end() - blocksTree(block.root->children.front()).node->begin(); - - thread_item->setTimeSmart(COL_DURATION, _units, duration); - thread_item->setBackgroundColor(::profiler_gui::SELECTED_THREAD_BACKGROUND); - thread_item->setTextColor(::profiler_gui::SELECTED_THREAD_FOREGROUND); - - // Sum of all children durations: - thread_item->setTimeSmart(COL_SELF_DURATION, _units, block.root->profiled_time); - - threadsMap.insert(::std::make_pair(block.root->thread_id, thread_item)); - - firstCswitch = 0; - auto it = ::std::lower_bound(block.root->sync.begin(), block.root->sync.end(), _left, [](::profiler::block_index_t ind, decltype(_left) _val) - { - return EASY_GLOBALS.gui_blocks[ind].tree.node->begin() < _val; - }); - +/************************************************************************ +* file name : tree_widget_loader.h +* ----------------- : +* creation time : 2016/08/18 +* author : Victor Zarubkin +* email : v.s.zarubkin@gmail.com +* ----------------- : +* description : The file contains implementation of EasyTreeWidgetLoader which aim is +* : to load EasyProfiler blocks hierarchy in separate thread. +* ----------------- : +* change log : * 2016/08/18 Victor Zarubkin: moved sources from blocks_tree_widget.h/.cpp +* : and renamed Prof* to Easy*. +* : +* : * +* ----------------- : +* license : Lightweight profiler library for c++ +* : Copyright(C) 2016 Sergey Yagovtsev, Victor Zarubkin +* : +* : +* : Licensed under 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. +* : +* : +* : GNU General Public License Usage +* : Alternatively, this file may be used 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 . +************************************************************************/ + +#include "tree_widget_loader.h" +#include "tree_widget_item.h" +#include "globals.h" + +#ifdef _WIN32 +#include +#endif + +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +////////////////////////////////////////////////////////////////////////// + +EasyTreeWidgetLoader::EasyTreeWidgetLoader() + : m_bDone(ATOMIC_VAR_INIT(false)) + , m_bInterrupt(ATOMIC_VAR_INIT(false)) + , m_progress(ATOMIC_VAR_INIT(0)) + , m_mode(EasyTreeMode_Full) +{ +} + +EasyTreeWidgetLoader::~EasyTreeWidgetLoader() +{ + interrupt(true); +} + +bool EasyTreeWidgetLoader::done() const +{ + return m_bDone.load(); +} + +void EasyTreeWidgetLoader::setDone() +{ + m_bDone.store(true); + //m_progress.store(100); +} + +void EasyTreeWidgetLoader::setProgress(int _progress) +{ + m_progress.store(_progress); +} + +bool EasyTreeWidgetLoader::interrupted() const +{ + return m_bInterrupt.load(); +} + +int EasyTreeWidgetLoader::progress() const +{ + return m_progress.load(); +} + +void EasyTreeWidgetLoader::takeTopLevelItems(ThreadedItems& _output) +{ + if (done()) + { + _output = ::std::move(m_topLevelItems); + m_topLevelItems.clear(); + } +} + +void EasyTreeWidgetLoader::takeItems(Items& _output) +{ + if (done()) + { + _output = ::std::move(m_items); + m_items.clear(); + } +} + +void EasyTreeWidgetLoader::interrupt(bool _wait) +{ + m_bInterrupt.store(true); + if (m_thread.joinable()) + m_thread.join(); + + m_bInterrupt.store(false); + m_bDone.store(false); + m_progress.store(0); + + if (!_wait) + { + auto deleter_thread = ::std::thread([](decltype(m_topLevelItems) _items) { + for (auto item : _items) + delete item.second; + }, ::std::move(m_topLevelItems)); + +#ifdef _WIN32 + SetThreadPriority(deleter_thread.native_handle(), THREAD_PRIORITY_LOWEST); +#endif + + deleter_thread.detach(); + } + else + { + for (auto item : m_topLevelItems) + delete item.second; + } + + m_items.clear(); + m_topLevelItems.clear(); + m_iditems.clear(); +} + +void EasyTreeWidgetLoader::fillTree(::profiler::timestamp_t& _beginTime, const unsigned int _blocksNumber, const ::profiler::thread_blocks_tree_t& _blocksTree, bool _colorizeRows, EasyTreeMode _mode) +{ + interrupt(); + m_mode = _mode; + m_thread = ::std::thread(&EasyTreeWidgetLoader::setTreeInternal1, this, + ::std::ref(_beginTime), _blocksNumber, ::std::ref(_blocksTree), _colorizeRows, + EASY_GLOBALS.add_zero_blocks_to_hierarchy, EASY_GLOBALS.use_decorated_thread_name, EASY_GLOBALS.time_units); +} + +void EasyTreeWidgetLoader::fillTreeBlocks(const::profiler_gui::TreeBlocks& _blocks, ::profiler::timestamp_t _beginTime, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict, bool _colorizeRows, EasyTreeMode _mode) +{ + interrupt(); + m_mode = _mode; + m_thread = ::std::thread(&EasyTreeWidgetLoader::setTreeInternal2, this, + _beginTime, ::std::ref(_blocks), _left, _right, _strict, _colorizeRows, + EASY_GLOBALS.add_zero_blocks_to_hierarchy, EASY_GLOBALS.use_decorated_thread_name, EASY_GLOBALS.time_units); +} + +////////////////////////////////////////////////////////////////////////// + +void EasyTreeWidgetLoader::setTreeInternal1(::profiler::timestamp_t& _beginTime, const unsigned int _blocksNumber, const ::profiler::thread_blocks_tree_t& _blocksTree, bool _colorizeRows, bool _addZeroBlocks, bool _decoratedThreadNames, ::profiler_gui::TimeUnits _units) +{ + m_items.reserve(_blocksNumber + _blocksTree.size()); // _blocksNumber does not include Thread root blocks + + ::profiler::timestamp_t finishtime = 0; + for (const auto& threadTree : _blocksTree) + { + const auto node_block = blocksTree(threadTree.second.children.front()).node; + const auto startTime = node_block->begin(); + const auto endTime = node_block->end(); + + if (_beginTime > startTime) + _beginTime = startTime; + + if (finishtime < endTime) + finishtime = endTime; + } + + //const QSignalBlocker b(this); + const auto u_thread = ::profiler_gui::toUnicode("thread"); + int i = 0; + const int total = static_cast(_blocksTree.size()); + for (const auto& threadTree : _blocksTree) + { + if (interrupted()) + break; + + const auto& root = threadTree.second; + auto item = new EasyTreeWidgetItem(); + item->setText(COL_NAME, ::profiler_gui::decoratedThreadName(_decoratedThreadNames, root, u_thread)); + + ::profiler::timestamp_t duration = 0; + if (!root.children.empty()) + duration = blocksTree(root.children.back()).node->end() - blocksTree(root.children.front()).node->begin(); + + item->setTimeSmart(COL_DURATION, _units, duration); + item->setBackgroundColor(::profiler_gui::SELECTED_THREAD_BACKGROUND); + item->setTextColor(::profiler_gui::SELECTED_THREAD_FOREGROUND); + + //_items.push_back(item); + + item->setTimeSmart(COL_SELF_DURATION, _units, root.profiled_time); + + ::profiler::timestamp_t children_duration = 0; + const auto children_items_number = setTreeInternal(root, 0, _beginTime, root.children, item, nullptr, _beginTime, finishtime + 1000000000ULL, false, children_duration, _colorizeRows, _addZeroBlocks, _units); + + if (children_items_number > 0) + { + //total_items += children_items_number + 1; + //addTopLevelItem(item); + //m_roots[threadTree.first] = item; + m_topLevelItems.emplace_back(root.thread_id, item); + } + else + { + //_items.pop_back(); + delete item; + } + + setProgress((100 * ++i) / total); + } + + setDone(); + //return total_items; +} + +////////////////////////////////////////////////////////////////////////// + +// auto calculateTotalChildrenNumber(const ::profiler::BlocksTree& _tree) -> decltype(_tree.children.size()) +// { +// auto children_number = _tree.children.size(); +// for (auto i : _tree.children) +// children_number += calculateTotalChildrenNumber(blocksTree(i)); +// return children_number; +// } + +typedef ::std::unordered_map<::profiler::thread_id_t, ::profiler::block_index_t, ::profiler_gui::do_no_hash<::profiler::thread_id_t>::hasher_t> BeginEndIndicesMap; + +void EasyTreeWidgetLoader::setTreeInternal2(const ::profiler::timestamp_t& _beginTime, const ::profiler_gui::TreeBlocks& _blocks, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict, bool _colorizeRows, bool _addZeroBlocks, bool _decoratedThreadNames, ::profiler_gui::TimeUnits _units) +{ + //size_t blocksNumber = 0; + //for (const auto& block : _blocks) + // blocksNumber += calculateTotalChildrenNumber(*block.tree); + // //blocksNumber += block.tree->total_children_number; + //m_items.reserve(blocksNumber + _blocks.size()); // blocksNumber does not include root blocks + + BeginEndIndicesMap beginEndMap; + RootsMap threadsMap; + + auto const setTree = (m_mode == EasyTreeMode_Full) ? &EasyTreeWidgetLoader::setTreeInternal : &EasyTreeWidgetLoader::setTreeInternalPlain; + + const auto u_thread = ::profiler_gui::toUnicode("thread"); + int i = 0, total = static_cast(_blocks.size()); + //const QSignalBlocker b(this); + for (const auto& block : _blocks) + { + if (interrupted()) + break; + + auto& gui_block = easyBlock(block.tree); + const auto startTime = gui_block.tree.node->begin(); + const auto endTime = gui_block.tree.node->end(); + if (startTime > _right || endTime < _left) + { + setProgress((90 * ++i) / total); + continue; + } + + ::profiler::timestamp_t duration = 0; + EasyTreeWidgetItem* thread_item = nullptr; + ::profiler::block_index_t& firstCswitch = beginEndMap[block.root->thread_id]; + auto thread_item_it = threadsMap.find(block.root->thread_id); + if (thread_item_it != threadsMap.end()) + { + thread_item = thread_item_it->second; + } + else + { + thread_item = new EasyTreeWidgetItem(); + thread_item->setText(COL_NAME, ::profiler_gui::decoratedThreadName(_decoratedThreadNames, *block.root, u_thread)); + + if (!block.root->children.empty()) + duration = blocksTree(block.root->children.back()).node->end() - blocksTree(block.root->children.front()).node->begin(); + + thread_item->setTimeSmart(COL_DURATION, _units, duration); + thread_item->setBackgroundColor(::profiler_gui::SELECTED_THREAD_BACKGROUND); + thread_item->setTextColor(::profiler_gui::SELECTED_THREAD_FOREGROUND); + + // Sum of all children durations: + thread_item->setTimeSmart(COL_SELF_DURATION, _units, block.root->profiled_time); + + threadsMap.insert(::std::make_pair(block.root->thread_id, thread_item)); + + firstCswitch = 0; + auto it = ::std::lower_bound(block.root->sync.begin(), block.root->sync.end(), _left, [](::profiler::block_index_t ind, decltype(_left) _val) + { + return EASY_GLOBALS.gui_blocks[ind].tree.node->begin() < _val; + }); + if (it != block.root->sync.end()) { firstCswitch = it - block.root->sync.begin(); @@ -321,667 +321,667 @@ void EasyTreeWidgetLoader::setTreeInternal2(const ::profiler::timestamp_t& _begi else { firstCswitch = static_cast<::profiler::block_index_t>(block.root->sync.size()); - } - } - - bool hasContextSwitch = false; - ::profiler::timestamp_t idleTime = 0; - for (::profiler::block_index_t ind = firstCswitch, ncs = static_cast<::profiler::block_index_t>(block.root->sync.size()); ind < ncs; ++ind) - { - auto cs_index = block.root->sync[ind]; - const auto cs = EASY_GLOBALS.gui_blocks[cs_index].tree.node; - - if (cs->begin() > endTime) - { - if (!hasContextSwitch) - firstCswitch = ind; - break; - } - - if (startTime <= cs->begin() && cs->end() <= endTime) - { - if (!hasContextSwitch) - { - firstCswitch = ind; - hasContextSwitch = true; - } - - idleTime += cs->duration(); - } - } - - auto item = new EasyTreeWidgetItem(block.tree, thread_item); - duration = endTime - startTime; - - auto name = *gui_block.tree.node->name() != 0 ? gui_block.tree.node->name() : easyDescriptor(gui_block.tree.node->id()).name(); - item->setText(COL_NAME, ::profiler_gui::toUnicode(name)); - item->setTimeSmart(COL_DURATION, _units, duration); - - auto active_time = duration - idleTime; - auto active_percent = duration == 0 ? 100. : ::profiler_gui::percentReal(active_time, duration); - item->setTimeSmart(COL_ACTIVE_TIME, _units, active_time); - item->setText(COL_ACTIVE_PERCENT, QString::number(active_percent, 'g', 3)); - item->setData(COL_ACTIVE_PERCENT, Qt::UserRole, active_percent); - - item->setTimeMs(COL_BEGIN, startTime - _beginTime); - item->setTimeMs(COL_END, endTime - _beginTime); - - item->setData(COL_PERCENT_PER_FRAME, Qt::UserRole, 0); - - auto percentage_per_thread = ::profiler_gui::percent(duration, block.root->profiled_time); - item->setData(COL_PERCENT_PER_PARENT, Qt::UserRole, percentage_per_thread); - item->setText(COL_PERCENT_PER_PARENT, QString::number(percentage_per_thread)); - - if (gui_block.tree.per_thread_stats != nullptr) // if there is per_thread_stats then there are other stats also - { - const ::profiler::BlockStatistics* per_thread_stats = gui_block.tree.per_thread_stats; - const ::profiler::BlockStatistics* per_parent_stats = gui_block.tree.per_parent_stats; - const ::profiler::BlockStatistics* per_frame_stats = gui_block.tree.per_frame_stats; - - - if (per_thread_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) - { - item->setTimeSmart(COL_MIN_PER_THREAD, _units, easyBlock(per_thread_stats->min_duration_block).tree.node->duration(), "min "); - item->setTimeSmart(COL_MAX_PER_THREAD, _units, easyBlock(per_thread_stats->max_duration_block).tree.node->duration(), "max "); - item->setTimeSmart(COL_AVERAGE_PER_THREAD, _units, per_thread_stats->average_duration()); - item->setTimeSmart(COL_DURATION_SUM_PER_THREAD, _units, per_thread_stats->total_duration); - } - - item->setData(COL_NCALLS_PER_THREAD, Qt::UserRole, per_thread_stats->calls_number); - item->setText(COL_NCALLS_PER_THREAD, QString::number(per_thread_stats->calls_number)); - - percentage_per_thread = ::profiler_gui::percent(per_thread_stats->total_duration, block.root->profiled_time); - item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, percentage_per_thread); - item->setText(COL_PERCENT_SUM_PER_THREAD, QString::number(percentage_per_thread)); - - - if (per_parent_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) - { - item->setTimeSmart(COL_MIN_PER_PARENT, _units, easyBlock(per_parent_stats->min_duration_block).tree.node->duration(), "min "); - item->setTimeSmart(COL_MAX_PER_PARENT, _units, easyBlock(per_parent_stats->max_duration_block).tree.node->duration(), "max "); - item->setTimeSmart(COL_AVERAGE_PER_PARENT, _units, per_parent_stats->average_duration()); - item->setTimeSmart(COL_DURATION_SUM_PER_PARENT, _units, per_parent_stats->total_duration); - } - - item->setData(COL_NCALLS_PER_PARENT, Qt::UserRole, per_parent_stats->calls_number); - item->setText(COL_NCALLS_PER_PARENT, QString::number(per_parent_stats->calls_number)); - - - if (per_frame_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) - { - item->setTimeSmart(COL_MIN_PER_FRAME, _units, easyBlock(per_frame_stats->min_duration_block).tree.node->duration(), "min "); - item->setTimeSmart(COL_MAX_PER_FRAME, _units, easyBlock(per_frame_stats->max_duration_block).tree.node->duration(), "max "); - item->setTimeSmart(COL_AVERAGE_PER_FRAME, _units, per_frame_stats->average_duration()); - item->setTimeSmart(COL_DURATION_SUM_PER_FRAME, _units, per_frame_stats->total_duration); - } - - item->setData(COL_NCALLS_PER_FRAME, Qt::UserRole, per_frame_stats->calls_number); - item->setText(COL_NCALLS_PER_FRAME, QString::number(per_frame_stats->calls_number)); - } - else - { - item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, 0); - item->setText(COL_PERCENT_SUM_PER_THREAD, ""); - } - - const auto color = easyDescriptor(gui_block.tree.node->id()).color(); - //const auto bgColor = ::profiler_gui::fromProfilerRgb(::profiler::colors::get_red(color), ::profiler::colors::get_green(color), ::profiler::colors::get_blue(color)); - const auto fgColor = ::profiler_gui::textColorForRgb(color);//0x00ffffff - bgColor; - item->setBackgroundColor(color); - item->setTextColor(fgColor); - -#ifdef EASY_TREE_WIDGET__USE_VECTOR - auto item_index = static_cast(m_items.size()); - m_items.push_back(item); -#endif - - size_t children_items_number = 0; - ::profiler::timestamp_t children_duration = 0; - if (!gui_block.tree.children.empty()) - { - m_iditems.clear(); - children_items_number = (this->*setTree)(*block.root, firstCswitch, _beginTime, gui_block.tree.children, item, item, _left, _right, _strict, children_duration, _colorizeRows, _addZeroBlocks, _units); - if (interrupted()) - break; - } - - int percentage = 100; - auto self_duration = duration - children_duration; - if (children_duration > 0 && duration > 0) - { - percentage = static_cast(0.5 + 100. * static_cast(self_duration) / static_cast(duration)); - } - - item->setTimeSmart(COL_SELF_DURATION, _units, self_duration); - item->setData(COL_SELF_DURATION_PERCENT, Qt::UserRole, percentage); - item->setText(COL_SELF_DURATION_PERCENT, QString::number(percentage)); - - if (children_items_number > 0 || !_strict || (startTime >= _left && endTime <= _right)) - { - //total_items += children_items_number + 1; -#ifdef EASY_TREE_WIDGET__USE_VECTOR - gui_block.tree_item = item_index; -#endif - - if (_colorizeRows) - item->colorize(_colorizeRows); - - if (gui_block.expanded) - item->setExpanded(true); - -#ifndef EASY_TREE_WIDGET__USE_VECTOR - m_items.insert(::std::make_pair(block.tree, item)); -#endif - } - else - { -#ifdef EASY_TREE_WIDGET__USE_VECTOR - m_items.pop_back(); -#endif - delete item; - } - - setProgress((90 * ++i) / total); - } - - i = 0; - total = static_cast(threadsMap.size()); - for (auto& it : threadsMap) - { - auto item = it.second; - - if (item->childCount() > 0) - { - //addTopLevelItem(item); - //m_roots[it.first] = item; - - //_items.push_back(item); - m_topLevelItems.emplace_back(it.first, item); - - //++total_items; - } - else - { - delete item; - } - - setProgress(90 + (10 * ++i) / total); - } - - setDone(); - //return total_items; -} - -////////////////////////////////////////////////////////////////////////// - -size_t EasyTreeWidgetLoader::setTreeInternal(const ::profiler::BlocksTreeRoot& _threadRoot, ::profiler::block_index_t _firstCswitch, const ::profiler::timestamp_t& _beginTime, const ::profiler::BlocksTree::children_t& _children, EasyTreeWidgetItem* _parent, EasyTreeWidgetItem* _frame, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict, ::profiler::timestamp_t& _duration, bool _colorizeRows, bool _addZeroBlocks, ::profiler_gui::TimeUnits _units) -{ - auto const setTree = m_mode == EasyTreeMode_Full ? &EasyTreeWidgetLoader::setTreeInternal : &EasyTreeWidgetLoader::setTreeInternalPlain; - - size_t total_items = 0; - for (auto child_index : _children) - { - if (interrupted()) - break; - - auto& gui_block = easyBlock(child_index); - const auto& child = gui_block.tree; - const auto startTime = child.node->begin(); - const auto endTime = child.node->end(); - const auto duration = endTime - startTime; - - if (duration == 0 && !_addZeroBlocks) - continue; - - _duration += duration; - - if (startTime > _right || endTime < _left) - continue; - - bool hasContextSwitch = false; - ::profiler::timestamp_t idleTime = 0; - for (::profiler::block_index_t ind = _firstCswitch, ncs = static_cast<::profiler::block_index_t>(_threadRoot.sync.size()); ind < ncs; ++ind) - { - auto cs_index = _threadRoot.sync[ind]; - const auto cs = EASY_GLOBALS.gui_blocks[cs_index].tree.node; - - if (cs->begin() > endTime) - { - if (!hasContextSwitch) - _firstCswitch = ind; - break; - } - - if (startTime <= cs->begin() && cs->end() <= endTime) - { - if (!hasContextSwitch) - { - _firstCswitch = ind; - hasContextSwitch = true; - } - - idleTime += cs->duration(); - } - } - - auto item = new EasyTreeWidgetItem(child_index, _parent); - - auto name = *child.node->name() != 0 ? child.node->name() : easyDescriptor(child.node->id()).name(); - item->setText(COL_NAME, ::profiler_gui::toUnicode(name)); - item->setTimeSmart(COL_DURATION, _units, duration); - - auto active_time = duration - idleTime; - auto active_percent = duration == 0 ? 100. : ::profiler_gui::percentReal(active_time, duration); - item->setTimeSmart(COL_ACTIVE_TIME, _units, active_time); - item->setText(COL_ACTIVE_PERCENT, QString::number(active_percent, 'g', 3)); - item->setData(COL_ACTIVE_PERCENT, Qt::UserRole, active_percent); - - item->setTimeMs(COL_BEGIN, startTime - _beginTime); - item->setTimeMs(COL_END, endTime - _beginTime); - item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, 0); - - if (child.per_thread_stats != nullptr) // if there is per_thread_stats then there are other stats also - { - const ::profiler::BlockStatistics* per_thread_stats = child.per_thread_stats; - const ::profiler::BlockStatistics* per_parent_stats = child.per_parent_stats; - const ::profiler::BlockStatistics* per_frame_stats = child.per_frame_stats; - - auto parent_duration = _parent->duration(); - auto percentage = duration == 0 ? 0 : ::profiler_gui::percent(duration, parent_duration); - auto percentage_sum = ::profiler_gui::percent(per_parent_stats->total_duration, parent_duration); - item->setData(COL_PERCENT_PER_PARENT, Qt::UserRole, percentage); - item->setText(COL_PERCENT_PER_PARENT, QString::number(percentage)); - item->setData(COL_PERCENT_SUM_PER_PARENT, Qt::UserRole, percentage_sum); - item->setText(COL_PERCENT_SUM_PER_PARENT, QString::number(percentage_sum)); - - if (_frame != nullptr) - { - if (_parent != _frame) - { - parent_duration = _frame->duration(); - percentage = duration == 0 ? 0 : ::profiler_gui::percent(duration, parent_duration); - percentage_sum = ::profiler_gui::percent(per_frame_stats->total_duration, parent_duration); - } - - item->setData(COL_PERCENT_PER_FRAME, Qt::UserRole, percentage); - item->setText(COL_PERCENT_PER_FRAME, QString::number(percentage)); - item->setData(COL_PERCENT_SUM_PER_FRAME, Qt::UserRole, percentage_sum); - item->setText(COL_PERCENT_SUM_PER_FRAME, QString::number(percentage_sum)); - } - else - { - item->setData(COL_PERCENT_PER_FRAME, Qt::UserRole, 0); - item->setData(COL_PERCENT_SUM_PER_FRAME, Qt::UserRole, 0); - - auto percentage_per_thread = ::profiler_gui::percent(duration, _threadRoot.profiled_time); - item->setData(COL_PERCENT_PER_PARENT, Qt::UserRole, percentage_per_thread); - item->setText(COL_PERCENT_PER_PARENT, QString::number(percentage_per_thread)); - } - - - if (per_thread_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) - { - item->setTimeSmart(COL_MIN_PER_THREAD, _units, easyBlock(per_thread_stats->min_duration_block).tree.node->duration(), "min "); - item->setTimeSmart(COL_MAX_PER_THREAD, _units, easyBlock(per_thread_stats->max_duration_block).tree.node->duration(), "max "); - item->setTimeSmart(COL_AVERAGE_PER_THREAD, _units, per_thread_stats->average_duration()); - item->setTimeSmart(COL_DURATION_SUM_PER_THREAD, _units, per_thread_stats->total_duration); - } - - item->setData(COL_NCALLS_PER_THREAD, Qt::UserRole, per_thread_stats->calls_number); - item->setText(COL_NCALLS_PER_THREAD, QString::number(per_thread_stats->calls_number)); - - auto percentage_per_thread = ::profiler_gui::percent(per_thread_stats->total_duration, _threadRoot.profiled_time); - item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, percentage_per_thread); - item->setText(COL_PERCENT_SUM_PER_THREAD, QString::number(percentage_per_thread)); - - - if (per_parent_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) - { - item->setTimeSmart(COL_MIN_PER_PARENT, _units, easyBlock(per_parent_stats->min_duration_block).tree.node->duration(), "min "); - item->setTimeSmart(COL_MAX_PER_PARENT, _units, easyBlock(per_parent_stats->max_duration_block).tree.node->duration(), "max "); - item->setTimeSmart(COL_AVERAGE_PER_PARENT, _units, per_parent_stats->average_duration()); - item->setTimeSmart(COL_DURATION_SUM_PER_PARENT, _units, per_parent_stats->total_duration); - } - - item->setData(COL_NCALLS_PER_PARENT, Qt::UserRole, per_parent_stats->calls_number); - item->setText(COL_NCALLS_PER_PARENT, QString::number(per_parent_stats->calls_number)); - - - if (per_frame_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) - { - item->setTimeSmart(COL_MIN_PER_FRAME, _units, easyBlock(per_frame_stats->min_duration_block).tree.node->duration(), "min "); - item->setTimeSmart(COL_MAX_PER_FRAME, _units, easyBlock(per_frame_stats->max_duration_block).tree.node->duration(), "max "); - item->setTimeSmart(COL_AVERAGE_PER_FRAME, _units, per_frame_stats->average_duration()); - item->setTimeSmart(COL_DURATION_SUM_PER_FRAME, _units, per_frame_stats->total_duration); - } - - item->setData(COL_NCALLS_PER_FRAME, Qt::UserRole, per_frame_stats->calls_number); - item->setText(COL_NCALLS_PER_FRAME, QString::number(per_frame_stats->calls_number)); - } - else - { - if (_frame == nullptr) - { - auto percentage_per_thread = ::profiler_gui::percent(duration, _threadRoot.profiled_time); - item->setData(COL_PERCENT_PER_PARENT, Qt::UserRole, percentage_per_thread); - item->setText(COL_PERCENT_PER_PARENT, QString::number(percentage_per_thread)); - } - else - { - item->setData(COL_PERCENT_PER_PARENT, Qt::UserRole, 0); - } - - item->setData(COL_PERCENT_SUM_PER_PARENT, Qt::UserRole, 0); - item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, 0); - } - - const auto color = easyDescriptor(child.node->id()).color(); - //const auto bgColor = ::profiler_gui::fromProfilerRgb(::profiler::colors::get_red(color), ::profiler::colors::get_green(color), ::profiler::colors::get_blue(color)); - const auto fgColor = ::profiler_gui::textColorForRgb(color);// 0x00ffffff - bgColor; - item->setBackgroundColor(color); - item->setTextColor(fgColor); - -#ifdef EASY_TREE_WIDGET__USE_VECTOR - auto item_index = static_cast(m_items.size()); - m_items.push_back(item); -#endif - - size_t children_items_number = 0; - ::profiler::timestamp_t children_duration = 0; - if (!child.children.empty()) - { - m_iditems.clear(); - children_items_number = (this->*setTree)(_threadRoot, _firstCswitch, _beginTime, child.children, item, _frame ? _frame : item, _left, _right, _strict, children_duration, _colorizeRows, _addZeroBlocks, _units); - if (interrupted()) - break; - } - - int percentage = 100; - auto self_duration = duration - children_duration; - if (children_duration > 0 && duration > 0) - { - percentage = ::profiler_gui::percent(self_duration, duration); - } - - item->setTimeSmart(COL_SELF_DURATION, _units, self_duration); - item->setData(COL_SELF_DURATION_PERCENT, Qt::UserRole, percentage); - item->setText(COL_SELF_DURATION_PERCENT, QString::number(percentage)); - - if (children_items_number > 0 || !_strict || (startTime >= _left && endTime <= _right)) - { - total_items += children_items_number + 1; -#ifdef EASY_TREE_WIDGET__USE_VECTOR - gui_block.tree_item = item_index; -#endif - - if (_colorizeRows) - item->colorize(_colorizeRows); - - if (gui_block.expanded) - item->setExpanded(true); - -#ifndef EASY_TREE_WIDGET__USE_VECTOR - m_items.insert(::std::make_pair(child_index, item)); -#endif - } - else - { -#ifdef EASY_TREE_WIDGET__USE_VECTOR - m_items.pop_back(); -#endif - delete item; - } - } - - return total_items; -} - -////////////////////////////////////////////////////////////////////////// - -::profiler::timestamp_t EasyTreeWidgetLoader::calculateChildrenDurationRecursive(const ::profiler::BlocksTree::children_t& _children, ::profiler::block_id_t _id) -{ - ::profiler::timestamp_t total_duration = 0; - - for (auto child_index : _children) - { - if (interrupted()) - break; - - const auto& gui_block = easyBlock(child_index); - total_duration += gui_block.tree.node->duration(); - if (gui_block.tree.node->id() == _id) - total_duration += calculateChildrenDurationRecursive(gui_block.tree.children, _id); - } - - return total_duration; -} - -size_t EasyTreeWidgetLoader::setTreeInternalPlain(const ::profiler::BlocksTreeRoot& _threadRoot, ::profiler::block_index_t _firstCswitch, const ::profiler::timestamp_t& _beginTime, const ::profiler::BlocksTree::children_t& _children, EasyTreeWidgetItem*, EasyTreeWidgetItem* _frame, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict, ::profiler::timestamp_t& _duration, bool _colorizeRows, bool _addZeroBlocks, ::profiler_gui::TimeUnits _units) -{ - size_t total_items = 0; - for (auto child_index : _children) - { - if (interrupted()) - break; - - const auto& gui_block = easyBlock(child_index); - const auto& child = gui_block.tree; - const auto startTime = child.node->begin(); - const auto endTime = child.node->end(); - const auto duration = endTime - startTime; - - _duration += duration; - - auto it = m_iditems.find(child.node->id()); - if (it != m_iditems.end()) - { - ++total_items; - - size_t children_items_number = 0; - ::profiler::timestamp_t children_duration = 0; - if (!child.children.empty()) - { - children_items_number = setTreeInternalPlain(_threadRoot, _firstCswitch, _beginTime, child.children, _frame, _frame, _left, _right, _strict, children_duration, _colorizeRows, _addZeroBlocks, _units); - if (interrupted()) - break; - } - - if (it->second != nullptr && child.per_frame_stats != nullptr) - { - auto item = it->second; - - //auto children_duration = calculateChildrenDurationRecursive(child.children, it->first); - if (children_duration != 0) - { - auto self_duration = item->data(COL_SELF_DURATION, Qt::UserRole).toULongLong() - children_duration; - - int percentage = 100; - if (child.per_frame_stats->total_duration > 0) - percentage = ::profiler_gui::percent(self_duration, child.per_frame_stats->total_duration); - - item->setTimeSmart(COL_SELF_DURATION, _units, self_duration); - item->setData(COL_SELF_DURATION_PERCENT, Qt::UserRole, percentage); - item->setText(COL_SELF_DURATION_PERCENT, QString::number(percentage)); - } - - bool hasContextSwitch = false; - ::profiler::timestamp_t idleTime = 0; - for (::profiler::block_index_t ind = _firstCswitch, ncs = static_cast<::profiler::block_index_t>(_threadRoot.sync.size()); ind < ncs; ++ind) - { - auto cs_index = _threadRoot.sync[ind]; - const auto cs = EASY_GLOBALS.gui_blocks[cs_index].tree.node; - - if (cs->begin() > endTime) - { - if (!hasContextSwitch) - _firstCswitch = ind; - break; - } - - if (startTime <= cs->begin() && cs->end() <= endTime) - { - if (!hasContextSwitch) - { - _firstCswitch = ind; - hasContextSwitch = true; - } - - idleTime += cs->duration(); - } - } - - auto active_time = item->data(COL_ACTIVE_TIME, Qt::UserRole).toULongLong() - idleTime; - auto active_percent = child.per_frame_stats->total_duration == 0 ? 100. : ::profiler_gui::percentReal(active_time, child.per_frame_stats->total_duration); - item->setTimeSmart(COL_ACTIVE_TIME, _units, active_time); - item->setText(COL_ACTIVE_PERCENT, QString::number(active_percent, 'g', 3)); - item->setData(COL_ACTIVE_PERCENT, Qt::UserRole, active_percent); - } - - continue; - } - - if (startTime > _right || endTime < _left) - continue; - - bool hasContextSwitch = false; - ::profiler::timestamp_t idleTime = 0; - for (::profiler::block_index_t ind = _firstCswitch, ncs = static_cast<::profiler::block_index_t>(_threadRoot.sync.size()); ind < ncs; ++ind) - { - auto cs_index = _threadRoot.sync[ind]; - const auto cs = EASY_GLOBALS.gui_blocks[cs_index].tree.node; - - if (cs->begin() > endTime) - { - if (!hasContextSwitch) - _firstCswitch = ind; - break; - } - - if (startTime <= cs->begin() && cs->end() <= endTime) - { - if (!hasContextSwitch) - { - _firstCswitch = ind; - hasContextSwitch = true; - } - - idleTime += cs->duration(); - } - } - - auto item = new EasyTreeWidgetItem(child_index, _frame); - - auto name = *child.node->name() != 0 ? child.node->name() : easyDescriptor(child.node->id()).name(); - item->setText(COL_NAME, ::profiler_gui::toUnicode(name)); - - if (child.per_thread_stats != nullptr) // if there is per_thread_stats then there are other stats also - { - const ::profiler::BlockStatistics* per_thread_stats = child.per_thread_stats; - if (per_thread_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) - { - item->setTimeSmart(COL_MIN_PER_THREAD, _units, easyBlock(per_thread_stats->min_duration_block).tree.node->duration(), "min "); - item->setTimeSmart(COL_MAX_PER_THREAD, _units, easyBlock(per_thread_stats->max_duration_block).tree.node->duration(), "max "); - item->setTimeSmart(COL_AVERAGE_PER_THREAD, _units, per_thread_stats->average_duration()); - } - - item->setTimeSmart(COL_DURATION_SUM_PER_THREAD, _units, per_thread_stats->total_duration); - item->setData(COL_NCALLS_PER_THREAD, Qt::UserRole, per_thread_stats->calls_number); - item->setText(COL_NCALLS_PER_THREAD, QString::number(per_thread_stats->calls_number)); - - auto percentage_per_thread = ::profiler_gui::percent(per_thread_stats->total_duration, _threadRoot.profiled_time); - item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, percentage_per_thread); - item->setText(COL_PERCENT_SUM_PER_THREAD, QString::number(percentage_per_thread)); - - const ::profiler::BlockStatistics* per_frame_stats = child.per_frame_stats; - const auto percentage_sum = ::profiler_gui::percent(per_frame_stats->total_duration, _frame->duration()); - item->setData(COL_PERCENT_PER_FRAME, Qt::UserRole, percentage_sum); - item->setText(COL_PERCENT_PER_FRAME, QString::number(percentage_sum)); - - if (per_frame_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) - { - item->setTimeSmart(COL_MIN_PER_FRAME, _units, easyBlock(per_frame_stats->min_duration_block).tree.node->duration(), "min "); - item->setTimeSmart(COL_MAX_PER_FRAME, _units, easyBlock(per_frame_stats->max_duration_block).tree.node->duration(), "max "); - item->setTimeSmart(COL_AVERAGE_PER_FRAME, _units, per_frame_stats->average_duration()); - } - - item->setTimeSmart(COL_DURATION, _units, per_frame_stats->total_duration); - item->setData(COL_NCALLS_PER_FRAME, Qt::UserRole, per_frame_stats->calls_number); - item->setText(COL_NCALLS_PER_FRAME, QString::number(per_frame_stats->calls_number)); - } - else - { - item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, 0); - item->setData(COL_PERCENT_PER_FRAME, Qt::UserRole, 0); - } - - const auto color = easyDescriptor(child.node->id()).color(); - const auto fgColor = ::profiler_gui::textColorForRgb(color);// 0x00ffffff - bgColor; - item->setBackgroundColor(color); - item->setTextColor(fgColor); - -#ifdef EASY_TREE_WIDGET__USE_VECTOR - auto item_index = static_cast(m_items.size()); - m_items.push_back(item); -#endif - m_iditems[child.node->id()] = nullptr; - - size_t children_items_number = 0; - ::profiler::timestamp_t children_duration = 0; - if (!child.children.empty()) - { - children_items_number = setTreeInternalPlain(_threadRoot, _firstCswitch, _beginTime, child.children, _frame, _frame, _left, _right, _strict, children_duration, _colorizeRows, _addZeroBlocks, _units); - if (interrupted()) - break; - } - - m_iditems[child.node->id()] = item; - - if (child.per_frame_stats != nullptr) - { - int percentage = 100; - auto self_duration = child.per_frame_stats->total_duration - children_duration; - if (child.per_frame_stats->total_duration > 0) - percentage = ::profiler_gui::percent(self_duration, child.per_frame_stats->total_duration); - - item->setTimeSmart(COL_SELF_DURATION, _units, self_duration); - item->setData(COL_SELF_DURATION_PERCENT, Qt::UserRole, percentage); - item->setText(COL_SELF_DURATION_PERCENT, QString::number(percentage)); - - auto active_time = child.per_frame_stats->total_duration - idleTime; - auto active_percent = child.per_frame_stats->total_duration == 0 ? 100. : ::profiler_gui::percentReal(active_time, child.per_frame_stats->total_duration); - item->setTimeSmart(COL_ACTIVE_TIME, _units, active_time); - item->setText(COL_ACTIVE_PERCENT, QString::number(active_percent, 'g', 3)); - item->setData(COL_ACTIVE_PERCENT, Qt::UserRole, active_percent); - } - - if (children_items_number > 0 || !_strict || (startTime >= _left && endTime <= _right)) - { - total_items += children_items_number + 1; -#ifdef EASY_TREE_WIDGET__USE_VECTOR - gui_block.tree_item = item_index; -#endif - - if (_colorizeRows) - item->colorize(_colorizeRows); - - if (gui_block.expanded) - item->setExpanded(true); - -#ifndef EASY_TREE_WIDGET__USE_VECTOR - m_items.insert(::std::make_pair(child_index, item)); -#endif - } - else - { -#ifdef EASY_TREE_WIDGET__USE_VECTOR - m_items.pop_back(); -#endif - delete item; - m_iditems.erase(gui_block.tree.node->id()); - } - } - - return total_items; -} - -////////////////////////////////////////////////////////////////////////// + } + } + + bool hasContextSwitch = false; + ::profiler::timestamp_t idleTime = 0; + for (::profiler::block_index_t ind = firstCswitch, ncs = static_cast<::profiler::block_index_t>(block.root->sync.size()); ind < ncs; ++ind) + { + auto cs_index = block.root->sync[ind]; + const auto cs = EASY_GLOBALS.gui_blocks[cs_index].tree.node; + + if (cs->begin() > endTime) + { + if (!hasContextSwitch) + firstCswitch = ind; + break; + } + + if (startTime <= cs->begin() && cs->end() <= endTime) + { + if (!hasContextSwitch) + { + firstCswitch = ind; + hasContextSwitch = true; + } + + idleTime += cs->duration(); + } + } + + auto item = new EasyTreeWidgetItem(block.tree, thread_item); + duration = endTime - startTime; + + auto name = *gui_block.tree.node->name() != 0 ? gui_block.tree.node->name() : easyDescriptor(gui_block.tree.node->id()).name(); + item->setText(COL_NAME, ::profiler_gui::toUnicode(name)); + item->setTimeSmart(COL_DURATION, _units, duration); + + auto active_time = duration - idleTime; + auto active_percent = duration == 0 ? 100. : ::profiler_gui::percentReal(active_time, duration); + item->setTimeSmart(COL_ACTIVE_TIME, _units, active_time); + item->setText(COL_ACTIVE_PERCENT, QString::number(active_percent, 'g', 3)); + item->setData(COL_ACTIVE_PERCENT, Qt::UserRole, active_percent); + + item->setTimeMs(COL_BEGIN, startTime - _beginTime); + item->setTimeMs(COL_END, endTime - _beginTime); + + item->setData(COL_PERCENT_PER_FRAME, Qt::UserRole, 0); + + auto percentage_per_thread = ::profiler_gui::percent(duration, block.root->profiled_time); + item->setData(COL_PERCENT_PER_PARENT, Qt::UserRole, percentage_per_thread); + item->setText(COL_PERCENT_PER_PARENT, QString::number(percentage_per_thread)); + + if (gui_block.tree.per_thread_stats != nullptr) // if there is per_thread_stats then there are other stats also + { + const ::profiler::BlockStatistics* per_thread_stats = gui_block.tree.per_thread_stats; + const ::profiler::BlockStatistics* per_parent_stats = gui_block.tree.per_parent_stats; + const ::profiler::BlockStatistics* per_frame_stats = gui_block.tree.per_frame_stats; + + + if (per_thread_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) + { + item->setTimeSmart(COL_MIN_PER_THREAD, _units, easyBlock(per_thread_stats->min_duration_block).tree.node->duration(), "min "); + item->setTimeSmart(COL_MAX_PER_THREAD, _units, easyBlock(per_thread_stats->max_duration_block).tree.node->duration(), "max "); + item->setTimeSmart(COL_AVERAGE_PER_THREAD, _units, per_thread_stats->average_duration()); + item->setTimeSmart(COL_DURATION_SUM_PER_THREAD, _units, per_thread_stats->total_duration); + } + + item->setData(COL_NCALLS_PER_THREAD, Qt::UserRole, per_thread_stats->calls_number); + item->setText(COL_NCALLS_PER_THREAD, QString::number(per_thread_stats->calls_number)); + + percentage_per_thread = ::profiler_gui::percent(per_thread_stats->total_duration, block.root->profiled_time); + item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, percentage_per_thread); + item->setText(COL_PERCENT_SUM_PER_THREAD, QString::number(percentage_per_thread)); + + + if (per_parent_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) + { + item->setTimeSmart(COL_MIN_PER_PARENT, _units, easyBlock(per_parent_stats->min_duration_block).tree.node->duration(), "min "); + item->setTimeSmart(COL_MAX_PER_PARENT, _units, easyBlock(per_parent_stats->max_duration_block).tree.node->duration(), "max "); + item->setTimeSmart(COL_AVERAGE_PER_PARENT, _units, per_parent_stats->average_duration()); + item->setTimeSmart(COL_DURATION_SUM_PER_PARENT, _units, per_parent_stats->total_duration); + } + + item->setData(COL_NCALLS_PER_PARENT, Qt::UserRole, per_parent_stats->calls_number); + item->setText(COL_NCALLS_PER_PARENT, QString::number(per_parent_stats->calls_number)); + + + if (per_frame_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) + { + item->setTimeSmart(COL_MIN_PER_FRAME, _units, easyBlock(per_frame_stats->min_duration_block).tree.node->duration(), "min "); + item->setTimeSmart(COL_MAX_PER_FRAME, _units, easyBlock(per_frame_stats->max_duration_block).tree.node->duration(), "max "); + item->setTimeSmart(COL_AVERAGE_PER_FRAME, _units, per_frame_stats->average_duration()); + item->setTimeSmart(COL_DURATION_SUM_PER_FRAME, _units, per_frame_stats->total_duration); + } + + item->setData(COL_NCALLS_PER_FRAME, Qt::UserRole, per_frame_stats->calls_number); + item->setText(COL_NCALLS_PER_FRAME, QString::number(per_frame_stats->calls_number)); + } + else + { + item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, 0); + item->setText(COL_PERCENT_SUM_PER_THREAD, ""); + } + + const auto color = easyDescriptor(gui_block.tree.node->id()).color(); + //const auto bgColor = ::profiler_gui::fromProfilerRgb(::profiler::colors::get_red(color), ::profiler::colors::get_green(color), ::profiler::colors::get_blue(color)); + const auto fgColor = ::profiler_gui::textColorForRgb(color);//0x00ffffff - bgColor; + item->setBackgroundColor(color); + item->setTextColor(fgColor); + +#ifdef EASY_TREE_WIDGET__USE_VECTOR + auto item_index = static_cast(m_items.size()); + m_items.push_back(item); +#endif + + size_t children_items_number = 0; + ::profiler::timestamp_t children_duration = 0; + if (!gui_block.tree.children.empty()) + { + m_iditems.clear(); + children_items_number = (this->*setTree)(*block.root, firstCswitch, _beginTime, gui_block.tree.children, item, item, _left, _right, _strict, children_duration, _colorizeRows, _addZeroBlocks, _units); + if (interrupted()) + break; + } + + int percentage = 100; + auto self_duration = duration - children_duration; + if (children_duration > 0 && duration > 0) + { + percentage = static_cast(0.5 + 100. * static_cast(self_duration) / static_cast(duration)); + } + + item->setTimeSmart(COL_SELF_DURATION, _units, self_duration); + item->setData(COL_SELF_DURATION_PERCENT, Qt::UserRole, percentage); + item->setText(COL_SELF_DURATION_PERCENT, QString::number(percentage)); + + if (children_items_number > 0 || !_strict || (startTime >= _left && endTime <= _right)) + { + //total_items += children_items_number + 1; +#ifdef EASY_TREE_WIDGET__USE_VECTOR + gui_block.tree_item = item_index; +#endif + + if (_colorizeRows) + item->colorize(_colorizeRows); + + if (gui_block.expanded) + item->setExpanded(true); + +#ifndef EASY_TREE_WIDGET__USE_VECTOR + m_items.insert(::std::make_pair(block.tree, item)); +#endif + } + else + { +#ifdef EASY_TREE_WIDGET__USE_VECTOR + m_items.pop_back(); +#endif + delete item; + } + + setProgress((90 * ++i) / total); + } + + i = 0; + total = static_cast(threadsMap.size()); + for (auto& it : threadsMap) + { + auto item = it.second; + + if (item->childCount() > 0) + { + //addTopLevelItem(item); + //m_roots[it.first] = item; + + //_items.push_back(item); + m_topLevelItems.emplace_back(it.first, item); + + //++total_items; + } + else + { + delete item; + } + + setProgress(90 + (10 * ++i) / total); + } + + setDone(); + //return total_items; +} + +////////////////////////////////////////////////////////////////////////// + +size_t EasyTreeWidgetLoader::setTreeInternal(const ::profiler::BlocksTreeRoot& _threadRoot, ::profiler::block_index_t _firstCswitch, const ::profiler::timestamp_t& _beginTime, const ::profiler::BlocksTree::children_t& _children, EasyTreeWidgetItem* _parent, EasyTreeWidgetItem* _frame, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict, ::profiler::timestamp_t& _duration, bool _colorizeRows, bool _addZeroBlocks, ::profiler_gui::TimeUnits _units) +{ + auto const setTree = m_mode == EasyTreeMode_Full ? &EasyTreeWidgetLoader::setTreeInternal : &EasyTreeWidgetLoader::setTreeInternalPlain; + + size_t total_items = 0; + for (auto child_index : _children) + { + if (interrupted()) + break; + + auto& gui_block = easyBlock(child_index); + const auto& child = gui_block.tree; + const auto startTime = child.node->begin(); + const auto endTime = child.node->end(); + const auto duration = endTime - startTime; + + if (duration == 0 && !_addZeroBlocks) + continue; + + _duration += duration; + + if (startTime > _right || endTime < _left) + continue; + + bool hasContextSwitch = false; + ::profiler::timestamp_t idleTime = 0; + for (::profiler::block_index_t ind = _firstCswitch, ncs = static_cast<::profiler::block_index_t>(_threadRoot.sync.size()); ind < ncs; ++ind) + { + auto cs_index = _threadRoot.sync[ind]; + const auto cs = EASY_GLOBALS.gui_blocks[cs_index].tree.node; + + if (cs->begin() > endTime) + { + if (!hasContextSwitch) + _firstCswitch = ind; + break; + } + + if (startTime <= cs->begin() && cs->end() <= endTime) + { + if (!hasContextSwitch) + { + _firstCswitch = ind; + hasContextSwitch = true; + } + + idleTime += cs->duration(); + } + } + + auto item = new EasyTreeWidgetItem(child_index, _parent); + + auto name = *child.node->name() != 0 ? child.node->name() : easyDescriptor(child.node->id()).name(); + item->setText(COL_NAME, ::profiler_gui::toUnicode(name)); + item->setTimeSmart(COL_DURATION, _units, duration); + + auto active_time = duration - idleTime; + auto active_percent = duration == 0 ? 100. : ::profiler_gui::percentReal(active_time, duration); + item->setTimeSmart(COL_ACTIVE_TIME, _units, active_time); + item->setText(COL_ACTIVE_PERCENT, QString::number(active_percent, 'g', 3)); + item->setData(COL_ACTIVE_PERCENT, Qt::UserRole, active_percent); + + item->setTimeMs(COL_BEGIN, startTime - _beginTime); + item->setTimeMs(COL_END, endTime - _beginTime); + item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, 0); + + if (child.per_thread_stats != nullptr) // if there is per_thread_stats then there are other stats also + { + const ::profiler::BlockStatistics* per_thread_stats = child.per_thread_stats; + const ::profiler::BlockStatistics* per_parent_stats = child.per_parent_stats; + const ::profiler::BlockStatistics* per_frame_stats = child.per_frame_stats; + + auto parent_duration = _parent->duration(); + auto percentage = duration == 0 ? 0 : ::profiler_gui::percent(duration, parent_duration); + auto percentage_sum = ::profiler_gui::percent(per_parent_stats->total_duration, parent_duration); + item->setData(COL_PERCENT_PER_PARENT, Qt::UserRole, percentage); + item->setText(COL_PERCENT_PER_PARENT, QString::number(percentage)); + item->setData(COL_PERCENT_SUM_PER_PARENT, Qt::UserRole, percentage_sum); + item->setText(COL_PERCENT_SUM_PER_PARENT, QString::number(percentage_sum)); + + if (_frame != nullptr) + { + if (_parent != _frame) + { + parent_duration = _frame->duration(); + percentage = duration == 0 ? 0 : ::profiler_gui::percent(duration, parent_duration); + percentage_sum = ::profiler_gui::percent(per_frame_stats->total_duration, parent_duration); + } + + item->setData(COL_PERCENT_PER_FRAME, Qt::UserRole, percentage); + item->setText(COL_PERCENT_PER_FRAME, QString::number(percentage)); + item->setData(COL_PERCENT_SUM_PER_FRAME, Qt::UserRole, percentage_sum); + item->setText(COL_PERCENT_SUM_PER_FRAME, QString::number(percentage_sum)); + } + else + { + item->setData(COL_PERCENT_PER_FRAME, Qt::UserRole, 0); + item->setData(COL_PERCENT_SUM_PER_FRAME, Qt::UserRole, 0); + + auto percentage_per_thread = ::profiler_gui::percent(duration, _threadRoot.profiled_time); + item->setData(COL_PERCENT_PER_PARENT, Qt::UserRole, percentage_per_thread); + item->setText(COL_PERCENT_PER_PARENT, QString::number(percentage_per_thread)); + } + + + if (per_thread_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) + { + item->setTimeSmart(COL_MIN_PER_THREAD, _units, easyBlock(per_thread_stats->min_duration_block).tree.node->duration(), "min "); + item->setTimeSmart(COL_MAX_PER_THREAD, _units, easyBlock(per_thread_stats->max_duration_block).tree.node->duration(), "max "); + item->setTimeSmart(COL_AVERAGE_PER_THREAD, _units, per_thread_stats->average_duration()); + item->setTimeSmart(COL_DURATION_SUM_PER_THREAD, _units, per_thread_stats->total_duration); + } + + item->setData(COL_NCALLS_PER_THREAD, Qt::UserRole, per_thread_stats->calls_number); + item->setText(COL_NCALLS_PER_THREAD, QString::number(per_thread_stats->calls_number)); + + auto percentage_per_thread = ::profiler_gui::percent(per_thread_stats->total_duration, _threadRoot.profiled_time); + item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, percentage_per_thread); + item->setText(COL_PERCENT_SUM_PER_THREAD, QString::number(percentage_per_thread)); + + + if (per_parent_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) + { + item->setTimeSmart(COL_MIN_PER_PARENT, _units, easyBlock(per_parent_stats->min_duration_block).tree.node->duration(), "min "); + item->setTimeSmart(COL_MAX_PER_PARENT, _units, easyBlock(per_parent_stats->max_duration_block).tree.node->duration(), "max "); + item->setTimeSmart(COL_AVERAGE_PER_PARENT, _units, per_parent_stats->average_duration()); + item->setTimeSmart(COL_DURATION_SUM_PER_PARENT, _units, per_parent_stats->total_duration); + } + + item->setData(COL_NCALLS_PER_PARENT, Qt::UserRole, per_parent_stats->calls_number); + item->setText(COL_NCALLS_PER_PARENT, QString::number(per_parent_stats->calls_number)); + + + if (per_frame_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) + { + item->setTimeSmart(COL_MIN_PER_FRAME, _units, easyBlock(per_frame_stats->min_duration_block).tree.node->duration(), "min "); + item->setTimeSmart(COL_MAX_PER_FRAME, _units, easyBlock(per_frame_stats->max_duration_block).tree.node->duration(), "max "); + item->setTimeSmart(COL_AVERAGE_PER_FRAME, _units, per_frame_stats->average_duration()); + item->setTimeSmart(COL_DURATION_SUM_PER_FRAME, _units, per_frame_stats->total_duration); + } + + item->setData(COL_NCALLS_PER_FRAME, Qt::UserRole, per_frame_stats->calls_number); + item->setText(COL_NCALLS_PER_FRAME, QString::number(per_frame_stats->calls_number)); + } + else + { + if (_frame == nullptr) + { + auto percentage_per_thread = ::profiler_gui::percent(duration, _threadRoot.profiled_time); + item->setData(COL_PERCENT_PER_PARENT, Qt::UserRole, percentage_per_thread); + item->setText(COL_PERCENT_PER_PARENT, QString::number(percentage_per_thread)); + } + else + { + item->setData(COL_PERCENT_PER_PARENT, Qt::UserRole, 0); + } + + item->setData(COL_PERCENT_SUM_PER_PARENT, Qt::UserRole, 0); + item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, 0); + } + + const auto color = easyDescriptor(child.node->id()).color(); + //const auto bgColor = ::profiler_gui::fromProfilerRgb(::profiler::colors::get_red(color), ::profiler::colors::get_green(color), ::profiler::colors::get_blue(color)); + const auto fgColor = ::profiler_gui::textColorForRgb(color);// 0x00ffffff - bgColor; + item->setBackgroundColor(color); + item->setTextColor(fgColor); + +#ifdef EASY_TREE_WIDGET__USE_VECTOR + auto item_index = static_cast(m_items.size()); + m_items.push_back(item); +#endif + + size_t children_items_number = 0; + ::profiler::timestamp_t children_duration = 0; + if (!child.children.empty()) + { + m_iditems.clear(); + children_items_number = (this->*setTree)(_threadRoot, _firstCswitch, _beginTime, child.children, item, _frame ? _frame : item, _left, _right, _strict, children_duration, _colorizeRows, _addZeroBlocks, _units); + if (interrupted()) + break; + } + + int percentage = 100; + auto self_duration = duration - children_duration; + if (children_duration > 0 && duration > 0) + { + percentage = ::profiler_gui::percent(self_duration, duration); + } + + item->setTimeSmart(COL_SELF_DURATION, _units, self_duration); + item->setData(COL_SELF_DURATION_PERCENT, Qt::UserRole, percentage); + item->setText(COL_SELF_DURATION_PERCENT, QString::number(percentage)); + + if (children_items_number > 0 || !_strict || (startTime >= _left && endTime <= _right)) + { + total_items += children_items_number + 1; +#ifdef EASY_TREE_WIDGET__USE_VECTOR + gui_block.tree_item = item_index; +#endif + + if (_colorizeRows) + item->colorize(_colorizeRows); + + if (gui_block.expanded) + item->setExpanded(true); + +#ifndef EASY_TREE_WIDGET__USE_VECTOR + m_items.insert(::std::make_pair(child_index, item)); +#endif + } + else + { +#ifdef EASY_TREE_WIDGET__USE_VECTOR + m_items.pop_back(); +#endif + delete item; + } + } + + return total_items; +} + +////////////////////////////////////////////////////////////////////////// + +::profiler::timestamp_t EasyTreeWidgetLoader::calculateChildrenDurationRecursive(const ::profiler::BlocksTree::children_t& _children, ::profiler::block_id_t _id) +{ + ::profiler::timestamp_t total_duration = 0; + + for (auto child_index : _children) + { + if (interrupted()) + break; + + const auto& gui_block = easyBlock(child_index); + total_duration += gui_block.tree.node->duration(); + if (gui_block.tree.node->id() == _id) + total_duration += calculateChildrenDurationRecursive(gui_block.tree.children, _id); + } + + return total_duration; +} + +size_t EasyTreeWidgetLoader::setTreeInternalPlain(const ::profiler::BlocksTreeRoot& _threadRoot, ::profiler::block_index_t _firstCswitch, const ::profiler::timestamp_t& _beginTime, const ::profiler::BlocksTree::children_t& _children, EasyTreeWidgetItem*, EasyTreeWidgetItem* _frame, ::profiler::timestamp_t _left, ::profiler::timestamp_t _right, bool _strict, ::profiler::timestamp_t& _duration, bool _colorizeRows, bool _addZeroBlocks, ::profiler_gui::TimeUnits _units) +{ + size_t total_items = 0; + for (auto child_index : _children) + { + if (interrupted()) + break; + + const auto& gui_block = easyBlock(child_index); + const auto& child = gui_block.tree; + const auto startTime = child.node->begin(); + const auto endTime = child.node->end(); + const auto duration = endTime - startTime; + + _duration += duration; + + auto it = m_iditems.find(child.node->id()); + if (it != m_iditems.end()) + { + ++total_items; + + size_t children_items_number = 0; + ::profiler::timestamp_t children_duration = 0; + if (!child.children.empty()) + { + children_items_number = setTreeInternalPlain(_threadRoot, _firstCswitch, _beginTime, child.children, _frame, _frame, _left, _right, _strict, children_duration, _colorizeRows, _addZeroBlocks, _units); + if (interrupted()) + break; + } + + if (it->second != nullptr && child.per_frame_stats != nullptr) + { + auto item = it->second; + + //auto children_duration = calculateChildrenDurationRecursive(child.children, it->first); + if (children_duration != 0) + { + auto self_duration = item->data(COL_SELF_DURATION, Qt::UserRole).toULongLong() - children_duration; + + int percentage = 100; + if (child.per_frame_stats->total_duration > 0) + percentage = ::profiler_gui::percent(self_duration, child.per_frame_stats->total_duration); + + item->setTimeSmart(COL_SELF_DURATION, _units, self_duration); + item->setData(COL_SELF_DURATION_PERCENT, Qt::UserRole, percentage); + item->setText(COL_SELF_DURATION_PERCENT, QString::number(percentage)); + } + + bool hasContextSwitch = false; + ::profiler::timestamp_t idleTime = 0; + for (::profiler::block_index_t ind = _firstCswitch, ncs = static_cast<::profiler::block_index_t>(_threadRoot.sync.size()); ind < ncs; ++ind) + { + auto cs_index = _threadRoot.sync[ind]; + const auto cs = EASY_GLOBALS.gui_blocks[cs_index].tree.node; + + if (cs->begin() > endTime) + { + if (!hasContextSwitch) + _firstCswitch = ind; + break; + } + + if (startTime <= cs->begin() && cs->end() <= endTime) + { + if (!hasContextSwitch) + { + _firstCswitch = ind; + hasContextSwitch = true; + } + + idleTime += cs->duration(); + } + } + + auto active_time = item->data(COL_ACTIVE_TIME, Qt::UserRole).toULongLong() - idleTime; + auto active_percent = child.per_frame_stats->total_duration == 0 ? 100. : ::profiler_gui::percentReal(active_time, child.per_frame_stats->total_duration); + item->setTimeSmart(COL_ACTIVE_TIME, _units, active_time); + item->setText(COL_ACTIVE_PERCENT, QString::number(active_percent, 'g', 3)); + item->setData(COL_ACTIVE_PERCENT, Qt::UserRole, active_percent); + } + + continue; + } + + if (startTime > _right || endTime < _left) + continue; + + bool hasContextSwitch = false; + ::profiler::timestamp_t idleTime = 0; + for (::profiler::block_index_t ind = _firstCswitch, ncs = static_cast<::profiler::block_index_t>(_threadRoot.sync.size()); ind < ncs; ++ind) + { + auto cs_index = _threadRoot.sync[ind]; + const auto cs = EASY_GLOBALS.gui_blocks[cs_index].tree.node; + + if (cs->begin() > endTime) + { + if (!hasContextSwitch) + _firstCswitch = ind; + break; + } + + if (startTime <= cs->begin() && cs->end() <= endTime) + { + if (!hasContextSwitch) + { + _firstCswitch = ind; + hasContextSwitch = true; + } + + idleTime += cs->duration(); + } + } + + auto item = new EasyTreeWidgetItem(child_index, _frame); + + auto name = *child.node->name() != 0 ? child.node->name() : easyDescriptor(child.node->id()).name(); + item->setText(COL_NAME, ::profiler_gui::toUnicode(name)); + + if (child.per_thread_stats != nullptr) // if there is per_thread_stats then there are other stats also + { + const ::profiler::BlockStatistics* per_thread_stats = child.per_thread_stats; + if (per_thread_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) + { + item->setTimeSmart(COL_MIN_PER_THREAD, _units, easyBlock(per_thread_stats->min_duration_block).tree.node->duration(), "min "); + item->setTimeSmart(COL_MAX_PER_THREAD, _units, easyBlock(per_thread_stats->max_duration_block).tree.node->duration(), "max "); + item->setTimeSmart(COL_AVERAGE_PER_THREAD, _units, per_thread_stats->average_duration()); + } + + item->setTimeSmart(COL_DURATION_SUM_PER_THREAD, _units, per_thread_stats->total_duration); + item->setData(COL_NCALLS_PER_THREAD, Qt::UserRole, per_thread_stats->calls_number); + item->setText(COL_NCALLS_PER_THREAD, QString::number(per_thread_stats->calls_number)); + + auto percentage_per_thread = ::profiler_gui::percent(per_thread_stats->total_duration, _threadRoot.profiled_time); + item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, percentage_per_thread); + item->setText(COL_PERCENT_SUM_PER_THREAD, QString::number(percentage_per_thread)); + + const ::profiler::BlockStatistics* per_frame_stats = child.per_frame_stats; + const auto percentage_sum = ::profiler_gui::percent(per_frame_stats->total_duration, _frame->duration()); + item->setData(COL_PERCENT_PER_FRAME, Qt::UserRole, percentage_sum); + item->setText(COL_PERCENT_PER_FRAME, QString::number(percentage_sum)); + + if (per_frame_stats->calls_number > 1 || !EASY_GLOBALS.display_only_relevant_stats) + { + item->setTimeSmart(COL_MIN_PER_FRAME, _units, easyBlock(per_frame_stats->min_duration_block).tree.node->duration(), "min "); + item->setTimeSmart(COL_MAX_PER_FRAME, _units, easyBlock(per_frame_stats->max_duration_block).tree.node->duration(), "max "); + item->setTimeSmart(COL_AVERAGE_PER_FRAME, _units, per_frame_stats->average_duration()); + } + + item->setTimeSmart(COL_DURATION, _units, per_frame_stats->total_duration); + item->setData(COL_NCALLS_PER_FRAME, Qt::UserRole, per_frame_stats->calls_number); + item->setText(COL_NCALLS_PER_FRAME, QString::number(per_frame_stats->calls_number)); + } + else + { + item->setData(COL_PERCENT_SUM_PER_THREAD, Qt::UserRole, 0); + item->setData(COL_PERCENT_PER_FRAME, Qt::UserRole, 0); + } + + const auto color = easyDescriptor(child.node->id()).color(); + const auto fgColor = ::profiler_gui::textColorForRgb(color);// 0x00ffffff - bgColor; + item->setBackgroundColor(color); + item->setTextColor(fgColor); + +#ifdef EASY_TREE_WIDGET__USE_VECTOR + auto item_index = static_cast(m_items.size()); + m_items.push_back(item); +#endif + m_iditems[child.node->id()] = nullptr; + + size_t children_items_number = 0; + ::profiler::timestamp_t children_duration = 0; + if (!child.children.empty()) + { + children_items_number = setTreeInternalPlain(_threadRoot, _firstCswitch, _beginTime, child.children, _frame, _frame, _left, _right, _strict, children_duration, _colorizeRows, _addZeroBlocks, _units); + if (interrupted()) + break; + } + + m_iditems[child.node->id()] = item; + + if (child.per_frame_stats != nullptr) + { + int percentage = 100; + auto self_duration = child.per_frame_stats->total_duration - children_duration; + if (child.per_frame_stats->total_duration > 0) + percentage = ::profiler_gui::percent(self_duration, child.per_frame_stats->total_duration); + + item->setTimeSmart(COL_SELF_DURATION, _units, self_duration); + item->setData(COL_SELF_DURATION_PERCENT, Qt::UserRole, percentage); + item->setText(COL_SELF_DURATION_PERCENT, QString::number(percentage)); + + auto active_time = child.per_frame_stats->total_duration - idleTime; + auto active_percent = child.per_frame_stats->total_duration == 0 ? 100. : ::profiler_gui::percentReal(active_time, child.per_frame_stats->total_duration); + item->setTimeSmart(COL_ACTIVE_TIME, _units, active_time); + item->setText(COL_ACTIVE_PERCENT, QString::number(active_percent, 'g', 3)); + item->setData(COL_ACTIVE_PERCENT, Qt::UserRole, active_percent); + } + + if (children_items_number > 0 || !_strict || (startTime >= _left && endTime <= _right)) + { + total_items += children_items_number + 1; +#ifdef EASY_TREE_WIDGET__USE_VECTOR + gui_block.tree_item = item_index; +#endif + + if (_colorizeRows) + item->colorize(_colorizeRows); + + if (gui_block.expanded) + item->setExpanded(true); + +#ifndef EASY_TREE_WIDGET__USE_VECTOR + m_items.insert(::std::make_pair(child_index, item)); +#endif + } + else + { +#ifdef EASY_TREE_WIDGET__USE_VECTOR + m_items.pop_back(); +#endif + delete item; + m_iditems.erase(gui_block.tree.node->id()); + } + } + + return total_items; +} + +////////////////////////////////////////////////////////////////////////// diff --git a/sample/main.cpp b/sample/main.cpp index 8fa03b9..2586525 100644 --- a/sample/main.cpp +++ b/sample/main.cpp @@ -1,240 +1,240 @@ -//#define FULL_DISABLE_PROFILER -#include "easy/profiler.h" -#include -#include -#include -#include -#include -#include "easy/reader.h" -#include -#include - -std::condition_variable cv; -std::mutex cv_m; -int g_i = 0; - -int OBJECTS = 500; -int MODELLING_STEPS = 1500; -int RENDER_STEPS = 1500; -int RESOURCE_LOADING_COUNT = 50; - -//#define SAMPLE_NETWORK_TEST - -void localSleep(int magic=200000) -{ - //PROFILER_BEGIN_FUNCTION_BLOCK_GROUPED(profiler::colors::Blue); - volatile int i = 0; - for (; i < magic; ++i); -} - -void loadingResources(){ - EASY_FUNCTION(profiler::colors::DarkCyan); - localSleep(); -// std::this_thread::sleep_for(std::chrono::milliseconds(50)); -} - -void prepareMath(){ - EASY_FUNCTION(profiler::colors::Green); - int* intarray = new int[OBJECTS]; - for (int i = 0; i < OBJECTS; ++i) - intarray[i] = i * i; - delete[] intarray; - //std::this_thread::sleep_for(std::chrono::milliseconds(3)); -} - -void calcIntersect(){ - EASY_FUNCTION(profiler::colors::Gold); - //int* intarray = new int[OBJECTS * OBJECTS]; - int* intarray = new int[OBJECTS]; - for (int i = 0; i < OBJECTS; ++i) - { - for (int j = i; j < OBJECTS; ++j) - //intarray[i * OBJECTS + j] = i * j - i / 2 + (OBJECTS - j) * 5; - intarray[j] = i * j - i / 2 + (OBJECTS - j) * 5; - } - delete[] intarray; - //std::this_thread::sleep_for(std::chrono::milliseconds(4)); -} - -double multModel(double i) -{ - EASY_FUNCTION(profiler::colors::PaleGold); - return i * sin(i) * cos(i); -} - -void calcPhys(){ - EASY_FUNCTION(profiler::colors::Amber); - double* intarray = new double[OBJECTS]; - for (int i = 0; i < OBJECTS; ++i) - intarray[i] = multModel(double(i)) + double(i / 3) - double((OBJECTS - i) / 2); - calcIntersect(); - delete[] intarray; -} - -double calcSubbrain(int i) -{ - EASY_FUNCTION(profiler::colors::Navy); - return i * i * i - i / 10 + (OBJECTS - i) * 7 ; -} - -void calcBrain(){ - EASY_FUNCTION(profiler::colors::LightBlue); - double* intarray = new double[OBJECTS]; - for (int i = 0; i < OBJECTS; ++i) - intarray[i] = calcSubbrain(i) + double(i * 180 / 3); - delete[] intarray; - //std::this_thread::sleep_for(std::chrono::milliseconds(3)); -} - -void calculateBehavior(){ - EASY_FUNCTION(profiler::colors::Blue); - calcPhys(); - calcBrain(); -} - -void modellingStep(){ - EASY_FUNCTION(); - prepareMath(); - calculateBehavior(); -} - -void prepareRender(){ - EASY_FUNCTION(profiler::colors::Brick); - localSleep(); - //std::this_thread::sleep_for(std::chrono::milliseconds(8)); - -} - -int multPhys(int i) -{ - EASY_FUNCTION(profiler::colors::Red700, profiler::ON); - return i * i * i * i / 100; -} - -int calcPhysicForObject(int i) -{ - EASY_FUNCTION(profiler::colors::Red); - return multPhys(i) + i / 3 - (OBJECTS - i) * 15; -} - -void calculatePhysics(){ - EASY_FUNCTION(profiler::colors::Red); - unsigned int* intarray = new unsigned int[OBJECTS]; - for (int i = 0; i < OBJECTS; ++i) - intarray[i] = calcPhysicForObject(i); - delete[] intarray; - //std::this_thread::sleep_for(std::chrono::milliseconds(8)); -} - -void frame(){ - EASY_FUNCTION(profiler::colors::Magenta); - prepareRender(); - calculatePhysics(); -} - -void loadingResourcesThread(){ - //std::unique_lock lk(cv_m); - //cv.wait(lk, []{return g_i == 1; }); - EASY_THREAD("Resource loading"); -#ifdef SAMPLE_NETWORK_TEST - while (true) { -#else - for(int i = 0; i < RESOURCE_LOADING_COUNT; i++){ -#endif - loadingResources(); - EASY_EVENT("Resources Loading!", profiler::colors::Cyan); - localSleep(1200000); - //std::this_thread::sleep_for(std::chrono::milliseconds(20)); - } -} - -void modellingThread(){ - //std::unique_lock lk(cv_m); - //cv.wait(lk, []{return g_i == 1; }); - EASY_THREAD("Modelling"); -#ifdef SAMPLE_NETWORK_TEST - while (true) { -#else - for (int i = 0; i < MODELLING_STEPS; i++){ -#endif - modellingStep(); - localSleep(1200000); - //std::this_thread::sleep_for(std::chrono::milliseconds(20)); - } -} - -void renderThread(){ - //std::unique_lock lk(cv_m); - //cv.wait(lk, []{return g_i == 1; }); - EASY_THREAD("Render"); -#ifdef SAMPLE_NETWORK_TEST - while (true) { -#else - for (int i = 0; i < RENDER_STEPS; i++){ -#endif - frame(); - localSleep(1200000); - //std::this_thread::sleep_for(std::chrono::milliseconds(20)); - } -} - -////////////////////////////////////////////////////////////////////////// - -int main(int argc, char* argv[]) -{ - if (argc > 1 && argv[1]){ - OBJECTS = std::atoi(argv[1]); - } - if (argc > 2 && argv[2]){ - MODELLING_STEPS = std::atoi(argv[2]); - } - if (argc > 3 && argv[3]){ - RENDER_STEPS = std::atoi(argv[3]); - } - if (argc > 4 && argv[4]){ - RESOURCE_LOADING_COUNT = std::atoi(argv[4]); - } - - std::cout << "Objects count: " << OBJECTS << std::endl; - std::cout << "Render steps: " << MODELLING_STEPS << std::endl; - std::cout << "Modelling steps: " << RENDER_STEPS << std::endl; - std::cout << "Resource loading count: " << RESOURCE_LOADING_COUNT << std::endl; - - auto start = std::chrono::system_clock::now(); - -#ifndef SAMPLE_NETWORK_TEST - EASY_PROFILER_ENABLE; -#endif - - EASY_MAIN_THREAD; - profiler::startListen(); - - std::vector threads; - for (int i=0; i < 3; i++) { - threads.emplace_back(loadingResourcesThread); - threads.emplace_back(renderThread); - threads.emplace_back(modellingThread); - } - - cv_m.lock(); - g_i = 1; - cv_m.unlock(); - cv.notify_all(); - - modellingThread(); - - for(auto& t : threads) - t.join(); - - auto end = std::chrono::system_clock::now(); - auto elapsed = - std::chrono::duration_cast(end - start); - - std::cout << "Elapsed time: " << elapsed.count() << " usec" << std::endl; - - auto blocks_count = profiler::dumpBlocksToFile("test.prof"); - - std::cout << "Blocks count: " << blocks_count << std::endl; - - return 0; -} +//#define FULL_DISABLE_PROFILER +#include "easy/profiler.h" +#include +#include +#include +#include +#include +#include "easy/reader.h" +#include +#include + +std::condition_variable cv; +std::mutex cv_m; +int g_i = 0; + +int OBJECTS = 500; +int MODELLING_STEPS = 1500; +int RENDER_STEPS = 1500; +int RESOURCE_LOADING_COUNT = 50; + +//#define SAMPLE_NETWORK_TEST + +void localSleep(int magic=200000) +{ + //PROFILER_BEGIN_FUNCTION_BLOCK_GROUPED(profiler::colors::Blue); + volatile int i = 0; + for (; i < magic; ++i); +} + +void loadingResources(){ + EASY_FUNCTION(profiler::colors::DarkCyan); + localSleep(); +// std::this_thread::sleep_for(std::chrono::milliseconds(50)); +} + +void prepareMath(){ + EASY_FUNCTION(profiler::colors::Green); + int* intarray = new int[OBJECTS]; + for (int i = 0; i < OBJECTS; ++i) + intarray[i] = i * i; + delete[] intarray; + //std::this_thread::sleep_for(std::chrono::milliseconds(3)); +} + +void calcIntersect(){ + EASY_FUNCTION(profiler::colors::Gold); + //int* intarray = new int[OBJECTS * OBJECTS]; + int* intarray = new int[OBJECTS]; + for (int i = 0; i < OBJECTS; ++i) + { + for (int j = i; j < OBJECTS; ++j) + //intarray[i * OBJECTS + j] = i * j - i / 2 + (OBJECTS - j) * 5; + intarray[j] = i * j - i / 2 + (OBJECTS - j) * 5; + } + delete[] intarray; + //std::this_thread::sleep_for(std::chrono::milliseconds(4)); +} + +double multModel(double i) +{ + EASY_FUNCTION(profiler::colors::PaleGold); + return i * sin(i) * cos(i); +} + +void calcPhys(){ + EASY_FUNCTION(profiler::colors::Amber); + double* intarray = new double[OBJECTS]; + for (int i = 0; i < OBJECTS; ++i) + intarray[i] = multModel(double(i)) + double(i / 3) - double((OBJECTS - i) / 2); + calcIntersect(); + delete[] intarray; +} + +double calcSubbrain(int i) +{ + EASY_FUNCTION(profiler::colors::Navy); + return i * i * i - i / 10 + (OBJECTS - i) * 7 ; +} + +void calcBrain(){ + EASY_FUNCTION(profiler::colors::LightBlue); + double* intarray = new double[OBJECTS]; + for (int i = 0; i < OBJECTS; ++i) + intarray[i] = calcSubbrain(i) + double(i * 180 / 3); + delete[] intarray; + //std::this_thread::sleep_for(std::chrono::milliseconds(3)); +} + +void calculateBehavior(){ + EASY_FUNCTION(profiler::colors::Blue); + calcPhys(); + calcBrain(); +} + +void modellingStep(){ + EASY_FUNCTION(); + prepareMath(); + calculateBehavior(); +} + +void prepareRender(){ + EASY_FUNCTION(profiler::colors::Brick); + localSleep(); + //std::this_thread::sleep_for(std::chrono::milliseconds(8)); + +} + +int multPhys(int i) +{ + EASY_FUNCTION(profiler::colors::Red700, profiler::ON); + return i * i * i * i / 100; +} + +int calcPhysicForObject(int i) +{ + EASY_FUNCTION(profiler::colors::Red); + return multPhys(i) + i / 3 - (OBJECTS - i) * 15; +} + +void calculatePhysics(){ + EASY_FUNCTION(profiler::colors::Red); + unsigned int* intarray = new unsigned int[OBJECTS]; + for (int i = 0; i < OBJECTS; ++i) + intarray[i] = calcPhysicForObject(i); + delete[] intarray; + //std::this_thread::sleep_for(std::chrono::milliseconds(8)); +} + +void frame(){ + EASY_FUNCTION(profiler::colors::Magenta); + prepareRender(); + calculatePhysics(); +} + +void loadingResourcesThread(){ + //std::unique_lock lk(cv_m); + //cv.wait(lk, []{return g_i == 1; }); + EASY_THREAD("Resource loading"); +#ifdef SAMPLE_NETWORK_TEST + while (true) { +#else + for(int i = 0; i < RESOURCE_LOADING_COUNT; i++){ +#endif + loadingResources(); + EASY_EVENT("Resources Loading!", profiler::colors::Cyan); + localSleep(1200000); + //std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } +} + +void modellingThread(){ + //std::unique_lock lk(cv_m); + //cv.wait(lk, []{return g_i == 1; }); + EASY_THREAD("Modelling"); +#ifdef SAMPLE_NETWORK_TEST + while (true) { +#else + for (int i = 0; i < MODELLING_STEPS; i++){ +#endif + modellingStep(); + localSleep(1200000); + //std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } +} + +void renderThread(){ + //std::unique_lock lk(cv_m); + //cv.wait(lk, []{return g_i == 1; }); + EASY_THREAD("Render"); +#ifdef SAMPLE_NETWORK_TEST + while (true) { +#else + for (int i = 0; i < RENDER_STEPS; i++){ +#endif + frame(); + localSleep(1200000); + //std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } +} + +////////////////////////////////////////////////////////////////////////// + +int main(int argc, char* argv[]) +{ + if (argc > 1 && argv[1]){ + OBJECTS = std::atoi(argv[1]); + } + if (argc > 2 && argv[2]){ + MODELLING_STEPS = std::atoi(argv[2]); + } + if (argc > 3 && argv[3]){ + RENDER_STEPS = std::atoi(argv[3]); + } + if (argc > 4 && argv[4]){ + RESOURCE_LOADING_COUNT = std::atoi(argv[4]); + } + + std::cout << "Objects count: " << OBJECTS << std::endl; + std::cout << "Render steps: " << MODELLING_STEPS << std::endl; + std::cout << "Modelling steps: " << RENDER_STEPS << std::endl; + std::cout << "Resource loading count: " << RESOURCE_LOADING_COUNT << std::endl; + + auto start = std::chrono::system_clock::now(); + +#ifndef SAMPLE_NETWORK_TEST + EASY_PROFILER_ENABLE; +#endif + + EASY_MAIN_THREAD; + profiler::startListen(); + + std::vector threads; + for (int i=0; i < 3; i++) { + threads.emplace_back(loadingResourcesThread); + threads.emplace_back(renderThread); + threads.emplace_back(modellingThread); + } + + cv_m.lock(); + g_i = 1; + cv_m.unlock(); + cv.notify_all(); + + modellingThread(); + + for(auto& t : threads) + t.join(); + + auto end = std::chrono::system_clock::now(); + auto elapsed = + std::chrono::duration_cast(end - start); + + std::cout << "Elapsed time: " << elapsed.count() << " usec" << std::endl; + + auto blocks_count = profiler::dumpBlocksToFile("test.prof"); + + std::cout << "Blocks count: " << blocks_count << std::endl; + + return 0; +}