mirror of
https://github.com/wolfpld/tracy.git
synced 2026-06-17 04:39:01 +00:00
Compare commits
19 Commits
slomp/stra
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b86ada40e | ||
|
|
616f33ff65 | ||
|
|
023cb20ba9 | ||
|
|
073cd266ac | ||
|
|
2e252d3988 | ||
|
|
77978b68ca | ||
|
|
d9939362f5 | ||
|
|
bba05bd3ad | ||
|
|
83a4a13cbd | ||
|
|
9e9aabe9a1 | ||
|
|
382a887ce9 | ||
|
|
fbd1c55151 | ||
|
|
398bab8041 | ||
|
|
69245e751b | ||
|
|
d571f2bd59 | ||
|
|
781938317d | ||
|
|
f9365abe4f | ||
|
|
3f91f35d59 | ||
|
|
1de94aa856 |
13
cmake/imgui-no-samplers.patch
Normal file
13
cmake/imgui-no-samplers.patch
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp
|
||||
index a9e32b7ac..2cdbc4812 100644
|
||||
--- a/backends/imgui_impl_opengl3.cpp
|
||||
+++ b/backends/imgui_impl_opengl3.cpp
|
||||
@@ -1069,7 +1069,7 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
|
||||
bd->HasPolygonMode = (!bd->GlProfileIsES2 && !bd->GlProfileIsES3);
|
||||
#endif
|
||||
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
|
||||
- bd->HasBindSampler = (bd->GlVersion >= 330 || bd->GlProfileIsES3);
|
||||
+ //bd->HasBindSampler = (bd->GlVersion >= 330 || bd->GlProfileIsES3);
|
||||
#endif
|
||||
bd->HasClipOrigin = (bd->GlVersion >= 450);
|
||||
#ifdef IMGUI_IMPL_OPENGL_HAS_EXTENSIONS
|
||||
@@ -142,6 +142,7 @@ CPMAddPackage(
|
||||
PATCHES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/imgui-emscripten.patch"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/imgui-loader.patch"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/imgui-no-samplers.patch"
|
||||
)
|
||||
|
||||
set(IMGUI_SOURCES
|
||||
@@ -271,7 +272,7 @@ if(NOT EMSCRIPTEN)
|
||||
CPMAddPackage(
|
||||
NAME pugixml
|
||||
GITHUB_REPOSITORY zeux/pugixml
|
||||
GIT_TAG v1.15
|
||||
GIT_TAG v1.16
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
add_library(TracyPugixml INTERFACE)
|
||||
|
||||
@@ -11,7 +11,7 @@ The user manual
|
||||
|
||||
**Bartosz Taudul** [\<wolf@nereid.pl\>](mailto:wolf@nereid.pl)
|
||||
|
||||
2026-06-15 <https://github.com/wolfpld/tracy>
|
||||
2026-06-16 <https://github.com/wolfpld/tracy>
|
||||
|
||||
# Quick overview {#quick-overview .unnumbered}
|
||||
|
||||
@@ -4301,6 +4301,8 @@ A single stack frame may have multiple function call places associated with it.
|
||||
|
||||
If the call stack shows a crash (see section [2.5](#crashhandling)), a red * Crash* label will be displayed. Clicking it will center the timeline on the crash. Note that the crash stack may contain OS or Tracy frames where the crash was intercepted and processed.
|
||||
|
||||
If the call stack shows a wait stack (see section [3.17.5.1](#waitstacks)), a blue * Wait stack* label will be displayed. Hovering the mouse pointer over it will display a tooltip displaying how much time was spent waiting in the stack, what was the wait reason and status.
|
||||
|
||||
Stack frame location may be displayed in the following number of ways, depending on the *Frame at* option selection:
|
||||
|
||||
- *Source code* -- displays source file and line number associated with the frame.
|
||||
@@ -4330,6 +4332,8 @@ Clicking on the * Summary* button will use Tracy Assist to generate a brief s
|
||||
|
||||
- Caret Right icon
|
||||
- Skull icon
|
||||
- Hourglass Half icon
|
||||
- Arrow Pointer icon
|
||||
- Shield Halved icon
|
||||
- Scissors icon
|
||||
- Door Open icon
|
||||
|
||||
@@ -4684,6 +4684,8 @@ A single stack frame may have multiple function call places associated with it.
|
||||
|
||||
If the call stack shows a crash (see section~\ref{crashhandling}), a red \emph{\faSkull{}~Crash} label will be displayed. Clicking it will center the timeline on the crash. Note that the crash stack may contain OS or Tracy frames where the crash was intercepted and processed.
|
||||
|
||||
If the call stack shows a wait stack (see section~\ref{waitstacks}), a blue \emph{\faHourglassHalf{}~Wait stack} label will be displayed. Hovering the \faArrowPointer{}~mouse pointer over it will display a tooltip displaying how much time was spent waiting in the stack, what was the wait reason and status.
|
||||
|
||||
Stack frame location may be displayed in the following number of ways, depending on the \emph{Frame~at} option selection:
|
||||
|
||||
\begin{itemize}
|
||||
|
||||
@@ -103,6 +103,12 @@ Tracy Profiler can intercept crashes and report them to the user for analysis. T
|
||||
|
||||
Each frame in a call stack has an associated instruction pointer, `ip` – the return address where the execution will return from the function a frame above. This address is somewhere in the symbol code. The start of the symbol is provided as the `baseAddr` value. This base address identifies the symbol and can be used in various symbol-related tool calls as the symbol address.
|
||||
|
||||
# Wait stacks
|
||||
|
||||
Some call stacks represent time spent waiting for something to happen. For example, the program may want to read something from the disk. In such cases, program execution will be paused, and the CPU will start running kernel code responsible for filesystem access, I/O routines, or just idling while waiting for a response from the hardware.
|
||||
|
||||
A wait stack is identified by the presence of the `wait_time` field, which shows how much time was spent waiting for execution to return to the program. Further information about the wait stack can be inferred from the optional fields `wait_reason` (with an explanation in `wait_reason_hint`) and `wait_state` (with an explanation in `wait_state_hint`).
|
||||
|
||||
# Inspecting call stacks
|
||||
|
||||
1. Focus on user's code. Ignore standard library boilerplate.
|
||||
|
||||
@@ -1531,11 +1531,18 @@ void View::AddLlmQuery( const char* query )
|
||||
#endif
|
||||
}
|
||||
|
||||
void View::ViewCallstack( uint32_t callstack, uint32_t thread )
|
||||
void View::ViewCallstack( uint32_t callstack, uint32_t thread, int64_t waitTime, const char* waitReason, const char* waitReasonCode, const char* waitState, const char* waitStateCode )
|
||||
{
|
||||
m_callstackView = {
|
||||
.id = callstack,
|
||||
.thread = thread
|
||||
.thread = thread,
|
||||
.wait = {
|
||||
.time = waitTime,
|
||||
.reason = waitReason,
|
||||
.reasonCode = waitReasonCode,
|
||||
.state = waitState,
|
||||
.stateCode = waitStateCode
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ public:
|
||||
void AddLlmAttachment( const nlohmann::json& json );
|
||||
void AddLlmQuery( const char* query );
|
||||
|
||||
void ViewCallstack( uint32_t callstack, uint32_t thread );
|
||||
void ViewCallstack( uint32_t callstack, uint32_t thread, int64_t waitTime = 0, const char* waitReason = nullptr, const char* waitReasonCode = nullptr, const char* waitState = nullptr, const char* waitStateCode = nullptr );
|
||||
|
||||
nlohmann::json GetCallstackJson( const CallstackFrameId* data, size_t size ) const;
|
||||
|
||||
@@ -261,10 +261,20 @@ private:
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
struct CallstackViewWait
|
||||
{
|
||||
int64_t time;
|
||||
const char* reason;
|
||||
const char* reasonCode;
|
||||
const char* state;
|
||||
const char* stateCode;
|
||||
};
|
||||
|
||||
struct CallstackView
|
||||
{
|
||||
uint32_t id;
|
||||
uint64_t thread;
|
||||
CallstackViewWait wait;
|
||||
};
|
||||
|
||||
void InitTextEditor();
|
||||
@@ -283,7 +293,7 @@ private:
|
||||
void DrawSampleList( const TimelineContext& ctx, const std::vector<SamplesDraw>& drawList, const Vector<SampleData>& vec, int offset, uint64_t tid );
|
||||
void DrawZoneList( const TimelineContext& ctx, const std::vector<TimelineDraw>& drawList, int offset, uint64_t tid, int maxDepth, double margin );
|
||||
void DrawThreadCropper( const int depth, const uint64_t tid, const float xPos, const float yPos, const float ostep, const float cropperWidth, const bool hasCtxSwitches );
|
||||
void DrawContextSwitchList( const TimelineContext& ctx, const std::vector<ContextSwitchDraw>& drawList, const Vector<ContextSwitchData>& ctxSwitch, int offset, int endOffset, bool isFiber );
|
||||
void DrawContextSwitchList( const TimelineContext& ctx, const std::vector<ContextSwitchDraw>& drawList, const Vector<ContextSwitchData>& ctxSwitch, int offset, int endOffset, bool isFiber, uint64_t tid );
|
||||
int DispatchGpuZoneLevel( const Vector<short_ptr<GpuEvent>>& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset, int depth, uint64_t thread, float yMin, float yMax, int64_t begin, int drift );
|
||||
template<typename Adapter, typename V>
|
||||
int DrawGpuZoneLevel( const V& vec, bool hover, double pxns, int64_t nspx, const ImVec2& wpos, int offset, int depth, uint64_t thread, float yMin, float yMax, int64_t begin, int drift );
|
||||
@@ -304,8 +314,8 @@ private:
|
||||
void DrawAllocList();
|
||||
void DrawCompare();
|
||||
void DrawCallstackWindow();
|
||||
void DrawCallstackTable( uint32_t callstack, uint64_t thread, bool globalEntriesButton, bool showThread );
|
||||
void DrawCallstackTable( const CallstackFrameId* data, size_t size, uint64_t thread, bool globalEntriesButton, bool showThread, bool hasCrashed = false, int64_t callstack = -1 );
|
||||
void DrawCallstackTable( uint32_t callstack, uint64_t thread = 0, const CallstackViewWait& wait = {}, bool globalEntriesButton = false, bool showThread = false );
|
||||
void DrawCallstackTable( const CallstackFrameId* data, size_t size, uint64_t thread = 0, const CallstackViewWait& wait = {}, bool globalEntriesButton = false, bool showThread = false, bool hasCrashed = false, int64_t callstack = -1 );
|
||||
void DrawMemoryAllocWindow();
|
||||
void DrawInfo();
|
||||
void DrawTextEditor();
|
||||
|
||||
@@ -24,22 +24,22 @@ void View::DrawCallstackWindow()
|
||||
ImGui::Begin( "Call stack", &show, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse );
|
||||
if( !ImGui::GetCurrentWindowRead()->SkipItems )
|
||||
{
|
||||
DrawCallstackTable( m_callstackView.id, m_callstackView.thread, true, true );
|
||||
DrawCallstackTable( m_callstackView.id, m_callstackView.thread, m_callstackView.wait, true, true );
|
||||
}
|
||||
ImGui::End();
|
||||
if( !show ) m_callstackView = {};
|
||||
}
|
||||
|
||||
void View::DrawCallstackTable( uint32_t callstack, uint64_t thread, bool globalEntriesButton, bool showThread )
|
||||
void View::DrawCallstackTable( uint32_t callstack, uint64_t thread, const CallstackViewWait& wait, bool globalEntriesButton, bool showThread )
|
||||
{
|
||||
auto& crash = m_worker.GetCrashEvent();
|
||||
const bool hasCrashed = crash.thread != 0 && crash.callstack == callstack;
|
||||
|
||||
auto& cs = m_worker.GetCallstack( callstack );
|
||||
DrawCallstackTable( cs.data(), cs.size(), thread, globalEntriesButton, showThread, hasCrashed, callstack );
|
||||
DrawCallstackTable( cs.data(), cs.size(), thread, wait, globalEntriesButton, showThread, hasCrashed, callstack );
|
||||
}
|
||||
|
||||
void View::DrawCallstackTable( const CallstackFrameId* data, size_t size, uint64_t thread, bool globalEntriesButton, bool showThread, bool hasCrashed, int64_t callstack )
|
||||
void View::DrawCallstackTable( const CallstackFrameId* data, size_t size, uint64_t thread, const CallstackViewWait& wait, bool globalEntriesButton, bool showThread, bool hasCrashed, int64_t callstack )
|
||||
{
|
||||
if( ClipboardButton() )
|
||||
{
|
||||
@@ -113,7 +113,7 @@ void View::DrawCallstackTable( const CallstackFrameId* data, size_t size, uint64
|
||||
}
|
||||
if( s_config.llm )
|
||||
{
|
||||
auto Attach = [this, data, size, hasCrashed, thread, callstack]() {
|
||||
auto Attach = [this, data, size, wait, hasCrashed, thread, callstack]() {
|
||||
auto json = GetCallstackJson( data, size );
|
||||
if( hasCrashed )
|
||||
{
|
||||
@@ -131,6 +131,14 @@ void View::DrawCallstackTable( const CallstackFrameId* data, size_t size, uint64
|
||||
json["thread_id"] = thread;
|
||||
}
|
||||
if( callstack >= 0 ) json["id"] = callstack;
|
||||
if( wait.time != 0 )
|
||||
{
|
||||
json["wait_time"] = TimeToString( wait.time );
|
||||
if( wait.reasonCode ) json["wait_reason"] = wait.reasonCode;
|
||||
if( wait.reason ) json["wait_reason_hint"] = wait.reason;
|
||||
if( wait.stateCode ) json["wait_state"] = wait.stateCode;
|
||||
if( wait.state ) json["wait_state_hint"] = wait.state;
|
||||
}
|
||||
|
||||
AddLlmAttachment( json );
|
||||
};
|
||||
@@ -201,6 +209,32 @@ void View::DrawCallstackTable( const CallstackFrameId* data, size_t size, uint64
|
||||
}
|
||||
}
|
||||
|
||||
if( wait.time != 0 )
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::Spacing();
|
||||
ImGui::SameLine();
|
||||
TextColoredUnformatted( ImVec4( 0.6f, 0.6f, 1.f, 1.f ), ICON_FA_HOURGLASS_HALF " Wait stack" );
|
||||
if( ImGui::IsItemHovered() )
|
||||
{
|
||||
ImGui::BeginTooltip();
|
||||
TextFocused( "Time:", TimeToString( wait.time ) );
|
||||
if( wait.reasonCode )
|
||||
{
|
||||
TextFocused( "Reason:", wait.reasonCode );
|
||||
ImGui::SameLine();
|
||||
TextDisabledUnformatted( wait.reason );
|
||||
}
|
||||
if( wait.stateCode )
|
||||
{
|
||||
TextFocused( "State:", wait.stateCode );
|
||||
ImGui::SameLine();
|
||||
TextDisabledUnformatted( wait.state );
|
||||
}
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
if( globalEntriesButton && m_worker.AreCallstackSamplesReady() )
|
||||
{
|
||||
auto frame = m_worker.GetCallstackFrame( *data );
|
||||
|
||||
@@ -214,6 +214,7 @@ bool View::DrawConnection()
|
||||
else
|
||||
{
|
||||
auto val = int( p.val );
|
||||
ImGui::SetNextItemWidth( 100 * GetScale() );
|
||||
if( ImGui::InputInt( "", &val, 1, 100, ImGuiInputTextFlags_EnterReturnsTrue ) )
|
||||
{
|
||||
m_worker.SetParameter( idx, int32_t( val ) );
|
||||
|
||||
@@ -166,7 +166,7 @@ const char* View::DecodeContextSwitchState( uint8_t state )
|
||||
}
|
||||
}
|
||||
|
||||
void View::DrawContextSwitchList( const TimelineContext& ctx, const std::vector<ContextSwitchDraw>& drawList, const Vector<ContextSwitchData>& ctxSwitch, int offset, int endOffset, bool isFiber )
|
||||
void View::DrawContextSwitchList( const TimelineContext& ctx, const std::vector<ContextSwitchDraw>& drawList, const Vector<ContextSwitchData>& ctxSwitch, int offset, int endOffset, bool isFiber, uint64_t tid )
|
||||
{
|
||||
constexpr float MinCtxSize = 4;
|
||||
|
||||
@@ -210,6 +210,12 @@ void View::DrawContextSwitchList( const TimelineContext& ctx, const std::vector<
|
||||
|
||||
if( hover )
|
||||
{
|
||||
int64_t waitTime = 0;
|
||||
const char* waitReason = nullptr;
|
||||
const char* waitReasonCode = nullptr;
|
||||
const char* waitState = nullptr;
|
||||
const char* waitStateCode = nullptr;
|
||||
|
||||
bool tooltip = false;
|
||||
if( ImGui::IsMouseHoveringRect( wpos + ImVec2( px0, offset ), wpos + ImVec2( pxw, offset + ty ) ) )
|
||||
{
|
||||
@@ -221,8 +227,9 @@ void View::DrawContextSwitchList( const TimelineContext& ctx, const std::vector<
|
||||
}
|
||||
else
|
||||
{
|
||||
waitTime = ev.WakeupVal() - prev.End();
|
||||
TextFocused( "Thread is", migration ? "migrating CPUs" : "waiting" );
|
||||
TextFocused( "Waiting time:", TimeToString( ev.WakeupVal() - prev.End() ) );
|
||||
TextFocused( "Waiting time:", TimeToString( waitTime ) );
|
||||
if( migration )
|
||||
{
|
||||
TextFocused( "CPU:", RealToString( prev.Cpu() ) );
|
||||
@@ -235,18 +242,22 @@ void View::DrawContextSwitchList( const TimelineContext& ctx, const std::vector<
|
||||
}
|
||||
if( prev.Reason() != 100 )
|
||||
{
|
||||
TextFocused( "Wait reason:", DecodeContextSwitchReasonCode( prev.Reason() ) );
|
||||
waitReason = DecodeContextSwitchReason( prev.Reason() );
|
||||
waitReasonCode = DecodeContextSwitchReasonCode( prev.Reason() );
|
||||
TextFocused( "Wait reason:", waitReasonCode );
|
||||
ImGui::SameLine();
|
||||
ImGui::PushFont( g_fonts.normal, FontSmall );
|
||||
ImGui::AlignTextToFramePadding();
|
||||
TextDisabledUnformatted( DecodeContextSwitchReason( prev.Reason() ) );
|
||||
TextDisabledUnformatted( waitReason );
|
||||
ImGui::PopFont();
|
||||
}
|
||||
TextFocused( "Wait state:", DecodeContextSwitchStateCode( prev.State() ) );
|
||||
waitState = DecodeContextSwitchState( prev.State() );
|
||||
waitStateCode = DecodeContextSwitchStateCode( prev.State() );
|
||||
TextFocused( "Wait state:", waitStateCode );
|
||||
ImGui::SameLine();
|
||||
ImGui::PushFont( g_fonts.normal, FontSmall );
|
||||
ImGui::AlignTextToFramePadding();
|
||||
TextDisabledUnformatted( DecodeContextSwitchState( prev.State() ) );
|
||||
TextDisabledUnformatted( waitState );
|
||||
ImGui::PopFont();
|
||||
}
|
||||
tooltip = true;
|
||||
@@ -275,16 +286,23 @@ void View::DrawContextSwitchList( const TimelineContext& ctx, const std::vector<
|
||||
const auto waitStack = v.data;
|
||||
if( waitStack )
|
||||
{
|
||||
ImGui::Separator();
|
||||
TextDisabledUnformatted( ICON_FA_HOURGLASS_HALF " Wait stack:" );
|
||||
CallstackTooltipContents( waitStack );
|
||||
if( ImGui::IsMouseClicked( 0 ) )
|
||||
{
|
||||
m_callstackView = {
|
||||
.id = waitStack,
|
||||
.thread = m_worker.DecompressThread( ev.Thread() )
|
||||
};
|
||||
}
|
||||
ImGui::Separator();
|
||||
TextDisabledUnformatted( ICON_FA_HOURGLASS_HALF " Wait stack:" );
|
||||
CallstackTooltipContents( waitStack );
|
||||
if( ImGui::IsMouseClicked( 0 ) )
|
||||
{
|
||||
m_callstackView = {
|
||||
.id = waitStack,
|
||||
.thread = tid,
|
||||
.wait = {
|
||||
.time = waitTime,
|
||||
.reason = waitReason,
|
||||
.reasonCode = waitReasonCode,
|
||||
.state = waitState,
|
||||
.stateCode = waitStateCode
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
@@ -616,7 +634,7 @@ void View::DrawWaitStacks()
|
||||
PrintStringPercent( buf, 100. * data[m_waitStack]->second / totalCount );
|
||||
TextDisabledUnformatted( buf );
|
||||
ImGui::Separator();
|
||||
DrawCallstackTable( data[m_waitStack]->first, 0, false, false );
|
||||
DrawCallstackTable( data[m_waitStack]->first );
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
|
||||
@@ -292,7 +292,7 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
|
||||
TextFocused( "Thread:", m_worker.GetThreadName( thread ) );
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled( "(%s)", RealToString( thread ) );
|
||||
|
||||
|
||||
m_drawThreadMigrations = thread;
|
||||
m_cpuDataThread = thread;
|
||||
}
|
||||
@@ -337,7 +337,7 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
|
||||
if( it != v.begin() )
|
||||
{
|
||||
auto& prev = *( it - 1 );
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
TextFocused( "Wait reason:", DecodeContextSwitchReasonCode( prev.Reason() ) );
|
||||
@@ -349,7 +349,7 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
|
||||
TextFocused( "Wait state:", DecodeContextSwitchStateCode( prev.State() ) );
|
||||
TextFocused( "Waiting time:", TimeToString( it->WakeupVal() - prev.End() ) );
|
||||
}
|
||||
|
||||
|
||||
// Do we have information about the readying thread?
|
||||
if( it->Start() - it->WakeupVal() )
|
||||
{
|
||||
@@ -369,7 +369,7 @@ bool View::DrawCpuData( const TimelineContext& ctx, const std::vector<CpuUsageDr
|
||||
bool wakeupThreadLocal, wakeupThreadUntracked;
|
||||
const char* wakeUpThreadProgram;
|
||||
auto wakeuplabel = GetThreadContextData( wakeupThread, wakeupThreadLocal, wakeupThreadUntracked, wakeUpThreadProgram );
|
||||
|
||||
|
||||
uint32_t wakeupThreadColor = getDisplayThreadColor( wakeupThread, wakeupThreadLocal, wakeupThreadUntracked );
|
||||
TextColoredUnformatted( HighlightColor<75>( wakeupThreadColor ), wakeuplabel );
|
||||
ImGui::SameLine();
|
||||
|
||||
@@ -1077,7 +1077,7 @@ void View::DrawZoneInfoWindow()
|
||||
{
|
||||
if( ImGui::TreeNode( "Call stack" ) )
|
||||
{
|
||||
DrawCallstackTable( m_worker.GetZoneExtra( ev ).callstack.Val(), tid, false, false );
|
||||
DrawCallstackTable( m_worker.GetZoneExtra( ev ).callstack.Val(), tid );
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
@@ -1091,7 +1091,7 @@ void View::DrawZoneInfoWindow()
|
||||
TextDisabledUnformatted( ICON_FA_WAND_SPARKLES );
|
||||
if( expand )
|
||||
{
|
||||
DrawCallstackTable( cs.data(), cs.size(), tid, false, false );
|
||||
DrawCallstackTable( cs.data(), cs.size(), tid );
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
@@ -1568,7 +1568,7 @@ void View::DrawGpuInfoWindow()
|
||||
{
|
||||
if( ImGui::TreeNode( "Call stack" ) )
|
||||
{
|
||||
DrawCallstackTable( ev.callstack.Val(), tid, false, false );
|
||||
DrawCallstackTable( ev.callstack.Val(), tid );
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ void View::DrawThread( const TimelineContext& ctx, const ThreadData& thread, con
|
||||
{
|
||||
auto ctxSwitch = m_worker.GetContextSwitchData( thread.id );
|
||||
assert( ctxSwitch );
|
||||
DrawContextSwitchList( ctx, ctxDraw, ctxSwitch->v, ctxOffset, offset, thread.isFiber );
|
||||
DrawContextSwitchList( ctx, ctxDraw, ctxSwitch->v, ctxOffset, offset, thread.isFiber, thread.id );
|
||||
}
|
||||
if( hasSamples && !samplesDraw.empty() )
|
||||
{
|
||||
|
||||
@@ -107,6 +107,12 @@ public:
|
||||
|
||||
assert( m_context != 255 );
|
||||
|
||||
if( !CheckFeature( "GL_ARB_timer_query" ) )
|
||||
{
|
||||
Profiler::LogString( MessageSourceType::Tracy, MessageSeverity::Warning, Color::Tomato, 0,
|
||||
"OpenGL context does not support GL_ARB_timer_query." );
|
||||
}
|
||||
|
||||
GLint bits;
|
||||
glGetQueryiv( GL_TIMESTAMP, GL_QUERY_COUNTER_BITS, &bits );
|
||||
if( bits == 0 )
|
||||
@@ -209,6 +215,30 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
// Returns whether the driver advertises a single extension (full GL_-prefixed token).
|
||||
static bool CheckFeature( const char* feature )
|
||||
{
|
||||
GLint major = 0;
|
||||
glGetIntegerv( GL_MAJOR_VERSION, &major );
|
||||
if( glGetError() != GL_NO_ERROR ) major = 0; // pre-3.0: enum not supported
|
||||
|
||||
if( major >= 3 )
|
||||
{
|
||||
GLint numExt = 0;
|
||||
glGetIntegerv( GL_NUM_EXTENSIONS, &numExt );
|
||||
for( GLint i = 0; i < numExt; i++ )
|
||||
{
|
||||
auto ext = (const char*)glGetStringi( GL_EXTENSIONS, i );
|
||||
if( ext && strcmp( ext, feature ) == 0 ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// pre GL3 fallback:
|
||||
auto exts = (const char*)glGetString( GL_EXTENSIONS );
|
||||
return exts && strstr( exts, feature ) != nullptr;
|
||||
}
|
||||
|
||||
#ifdef TRACY_OPENGL_AUTO_CALIBRATION
|
||||
// Monotonic host ns for the inter-calibration interval (cpuDelta), kept
|
||||
// separate from Profiler::GetTime() as in the D3D12/Vulkan backends.
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(NO_STATISTICS ON)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
project(
|
||||
tracy-import-strace
|
||||
LANGUAGES C CXX
|
||||
VERSION ${TRACY_VERSION_STRING}
|
||||
)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/config.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/vendor.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/server.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/GitRef.cmake)
|
||||
|
||||
add_executable(tracy-import-strace
|
||||
src/stracy.cpp
|
||||
)
|
||||
add_git_ref(tracy-import-strace)
|
||||
target_link_libraries(tracy-import-strace PRIVATE TracyServer)
|
||||
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT tracy-import-strace)
|
||||
@@ -1,460 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "../../server/TracyFileWrite.hpp"
|
||||
#include "../../server/TracyWorker.hpp"
|
||||
#include "../../public/common/TracyVersion.hpp"
|
||||
#include "GitRef.hpp"
|
||||
|
||||
static void Usage()
|
||||
{
|
||||
printf( "tracy-import-strace %d.%d.%d / %s\n\n",
|
||||
tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch, tracy::GitRef );
|
||||
printf( "Usage: tracy-import-strace input.strace output.tracy\n" );
|
||||
printf( " tracy-import-strace - output.tracy (read from stdin)\n\n" );
|
||||
printf( "Recommended strace invocation:\n" );
|
||||
printf( " strace -ttt -T -f -o trace.log <program> [args...]\n" );
|
||||
printf( " strace -ttt -T -f -p <pid> -o trace.log\n\n" );
|
||||
printf( "Mapped events:\n" );
|
||||
printf( " syscall entry/return -> timeline zones\n" );
|
||||
printf( " signals -> messages\n" );
|
||||
printf( " process exit/kill -> messages\n" );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
// Parse "SSSSSSSSSS.UUUUUU" → nanoseconds without floating-point precision loss.
|
||||
// Advances *p past the consumed characters. Returns UINT64_MAX on parse error.
|
||||
static uint64_t ParseTimestamp( const char*& p )
|
||||
{
|
||||
if( !isdigit( (unsigned char)*p ) ) return UINT64_MAX;
|
||||
|
||||
uint64_t sec = 0;
|
||||
while( isdigit( (unsigned char)*p ) )
|
||||
sec = sec * 10 + ( *p++ - '0' );
|
||||
|
||||
if( *p != '.' ) return UINT64_MAX;
|
||||
p++;
|
||||
|
||||
uint64_t usec = 0;
|
||||
int digits = 0;
|
||||
while( isdigit( (unsigned char)*p ) && digits < 6 )
|
||||
{
|
||||
usec = usec * 10 + ( *p++ - '0' );
|
||||
digits++;
|
||||
}
|
||||
for( ; digits < 6; digits++ ) usec *= 10; // pad to microseconds if fewer digits
|
||||
while( isdigit( (unsigned char)*p ) ) p++; // discard extra precision
|
||||
|
||||
return sec * 1000000000ULL + usec * 1000ULL;
|
||||
}
|
||||
|
||||
// Consume "[pid N]" prefix and return N, or return default_tid if not present.
|
||||
// Advances *p past the consumed characters (including trailing space).
|
||||
static uint64_t ConsumePidPrefix( const char*& p, uint64_t default_tid )
|
||||
{
|
||||
if( p[0] != '[' || strncmp( p, "[pid", 4 ) != 0 ) return default_tid;
|
||||
|
||||
const char* q = p + 4;
|
||||
while( *q == ' ' ) q++;
|
||||
if( !isdigit( (unsigned char)*q ) ) return default_tid;
|
||||
|
||||
uint64_t tid = 0;
|
||||
while( isdigit( (unsigned char)*q ) )
|
||||
tid = tid * 10 + ( *q++ - '0' );
|
||||
|
||||
while( *q == ' ' ) q++;
|
||||
if( *q != ']' ) return default_tid;
|
||||
|
||||
p = q + 1;
|
||||
return tid;
|
||||
}
|
||||
|
||||
// Parse "<N.NNNNNN>" trailing duration → nanoseconds. Returns 0 if not present.
|
||||
static uint64_t ParseTrailingDuration( const char* line )
|
||||
{
|
||||
const char* p = line + strlen( line );
|
||||
while( p > line && strchr( " \r\n", p[-1] ) ) p--;
|
||||
if( p == line || p[-1] != '>' ) return 0;
|
||||
p--;
|
||||
|
||||
const char* end = p;
|
||||
while( p > line && p[-1] != '<' ) p--;
|
||||
if( p == line || p[-1] != '<' ) return 0;
|
||||
|
||||
char tmp[32];
|
||||
size_t len = end - p;
|
||||
if( len == 0 || len >= sizeof( tmp ) ) return 0;
|
||||
memcpy( tmp, p, len );
|
||||
tmp[len] = '\0';
|
||||
|
||||
double dur_sec = 0.0;
|
||||
if( sscanf( tmp, "%lf", &dur_sec ) != 1 ) return 0;
|
||||
return (uint64_t)( dur_sec * 1e9 );
|
||||
}
|
||||
|
||||
// Return the syscall name — the identifier before the first '('. Empty on failure.
|
||||
static std::string ParseSyscallName( const char* p )
|
||||
{
|
||||
const char* start = p;
|
||||
while( *p && *p != '(' && *p != ' ' && *p != '\n' ) p++;
|
||||
if( p == start || *p != '(' ) return {};
|
||||
return std::string( start, p );
|
||||
}
|
||||
|
||||
// Extract syscall arguments for a COMPLETE call line.
|
||||
// Finds the last " = " (the retval separator), then the ')' before it.
|
||||
// This correctly handles " = " appearing inside string arguments.
|
||||
static std::string ExtractArgsComplete( const char* line_start )
|
||||
{
|
||||
const char* open = strchr( line_start, '(' );
|
||||
if( !open ) return {};
|
||||
const char* args = open + 1;
|
||||
|
||||
// Walk the whole line to find the last " = "
|
||||
const char* last_eq = nullptr;
|
||||
for( const char* p = args; *p; p++ )
|
||||
if( p[0] == ' ' && p[1] == '=' && p[2] == ' ' )
|
||||
last_eq = p;
|
||||
|
||||
if( !last_eq ) return {};
|
||||
|
||||
// Find the closing ')' that immediately precedes " = "
|
||||
const char* close = last_eq;
|
||||
while( close > args && *close != ')' ) close--;
|
||||
if( close <= args ) return {};
|
||||
|
||||
return std::string( args, close );
|
||||
}
|
||||
|
||||
// Extract syscall arguments for an UNFINISHED call line, up to the delimiter.
|
||||
static std::string ExtractArgsUnfinished( const char* line_start, const char* delim )
|
||||
{
|
||||
const char* open = strchr( line_start, '(' );
|
||||
if( !open ) return {};
|
||||
const char* args = open + 1;
|
||||
|
||||
const char* end = strstr( args, delim );
|
||||
if( !end || end <= args ) return {};
|
||||
while( end > args && end[-1] == ' ' ) end--; // trim trailing space
|
||||
return std::string( args, end );
|
||||
}
|
||||
|
||||
// Extract a quoted string from strace arg output, e.g. the "name" in
|
||||
// prctl(PR_SET_NAME, "name") = 0. Returns empty string if not found.
|
||||
static std::string ExtractQuotedString( const char* p )
|
||||
{
|
||||
const char* open = strchr( p, '"' );
|
||||
if( !open ) return {};
|
||||
open++;
|
||||
const char* close = strchr( open, '"' );
|
||||
if( !close ) return {};
|
||||
return std::string( open, close );
|
||||
}
|
||||
|
||||
// Parse the integer return value from a complete strace line " = N <dur>".
|
||||
// Returns -1 on failure.
|
||||
static int64_t ParseRetval( const char* line )
|
||||
{
|
||||
const char* eq = strstr( line, " = " );
|
||||
if( !eq ) return -1;
|
||||
const char* p = eq + 3;
|
||||
bool neg = ( *p == '-' );
|
||||
if( neg ) p++;
|
||||
if( !isdigit( (unsigned char)*p ) ) return -1;
|
||||
int64_t v = 0;
|
||||
while( isdigit( (unsigned char)*p ) ) v = v * 10 + (*p++ - '0');
|
||||
return neg ? -v : v;
|
||||
}
|
||||
|
||||
// Extract the function name from a strace -k frame line, e.g.:
|
||||
// "/lib/libc.so.6(__read_nocancel+0x7) [0xf1e07]" → "__read_nocancel"
|
||||
static std::string ParseFrameName( const std::string& frame )
|
||||
{
|
||||
const char* p = frame.c_str();
|
||||
const char* open = strchr( p, '(' );
|
||||
if( !open ) return frame;
|
||||
const char* name = open + 1;
|
||||
const char* plus = strchr( name, '+' );
|
||||
const char* close = strchr( name, ')' );
|
||||
const char* end = ( plus && ( !close || plus < close ) ) ? plus : close;
|
||||
if( !end ) return frame;
|
||||
return std::string( name, end );
|
||||
}
|
||||
|
||||
struct PendingEntry
|
||||
{
|
||||
uint64_t ts_begin;
|
||||
std::string syscall_name;
|
||||
std::string args_text;
|
||||
};
|
||||
|
||||
// A complete or resumed syscall zone held until its trailing callstack frames are collected.
|
||||
struct PendingComplete
|
||||
{
|
||||
uint64_t tid;
|
||||
uint64_t ts_begin;
|
||||
uint64_t ts_end;
|
||||
std::string name;
|
||||
std::string args;
|
||||
std::vector<std::string> frames; // innermost-first, as strace emits them
|
||||
};
|
||||
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
if( argc != 3 ) Usage();
|
||||
|
||||
const char* input_path = argv[1];
|
||||
const char* output_path = argv[2];
|
||||
|
||||
FILE* fin = ( strcmp( input_path, "-" ) == 0 ) ? stdin : fopen( input_path, "r" );
|
||||
if( !fin )
|
||||
{
|
||||
fprintf( stderr, "Cannot open input: %s\n", input_path );
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<tracy::Worker::ImportEventTimeline> timeline;
|
||||
std::vector<tracy::Worker::ImportEventMessages> messages;
|
||||
std::vector<tracy::Worker::ImportEventPlots> plots; // required by Worker API; unused
|
||||
std::unordered_map<uint64_t, std::string> threadNames;
|
||||
std::unordered_map<uint64_t, PendingEntry> pending; // tid → in-flight syscall
|
||||
|
||||
// Holds a complete/resumed zone while we collect its trailing callstack frames.
|
||||
std::optional<PendingComplete> pending_complete;
|
||||
|
||||
// Emit a pending zone as nested Tracy zones:
|
||||
// outer callstack frames (one begin per frame, outermost first)
|
||||
// syscall zone (begin + end)
|
||||
// outer callstack frames (one end per frame, innermost first)
|
||||
//
|
||||
// frames[0] is the innermost frame (the libc/kernel syscall stub) and is
|
||||
// skipped — it is already represented by the syscall zone itself.
|
||||
auto flushComplete = [&]()
|
||||
{
|
||||
if( !pending_complete ) return;
|
||||
const auto& pc = *pending_complete;
|
||||
|
||||
const size_t user_start = pc.frames.empty() ? 0 : 1; // skip innermost stub
|
||||
|
||||
// Open outer frames, outermost first (= reversed from strace order).
|
||||
for( int i = (int)pc.frames.size() - 1; i >= (int)user_start; i-- )
|
||||
timeline.push_back( { pc.tid, pc.ts_begin,
|
||||
ParseFrameName( pc.frames[i] ), pc.frames[i],
|
||||
false, "", 0 } );
|
||||
|
||||
// The syscall zone itself.
|
||||
timeline.push_back( { pc.tid, pc.ts_begin, pc.name, pc.args, false, "", 0 } );
|
||||
timeline.push_back( { pc.tid, pc.ts_end, "", "", true, "", 0 } );
|
||||
|
||||
// Close outer frames, innermost first.
|
||||
for( size_t i = user_start; i < pc.frames.size(); i++ )
|
||||
timeline.push_back( { pc.tid, pc.ts_end, "", "", true, "", 0 } );
|
||||
|
||||
pending_complete.reset();
|
||||
};
|
||||
|
||||
char buf[65536];
|
||||
// TID used when strace output has no [pid N] prefix (single-threaded traces or
|
||||
// the very first calls of a process before any fork).
|
||||
constexpr uint64_t kDefaultTid = 0;
|
||||
|
||||
while( fgets( buf, sizeof( buf ), fin ) )
|
||||
{
|
||||
const char* p = buf;
|
||||
|
||||
// Callstack frame from strace -k: " > /path/lib(func+0xoff) [0xaddr]"
|
||||
// Must be checked before the isdigit guard.
|
||||
if( p[0] == ' ' && p[1] == '>' )
|
||||
{
|
||||
if( pending_complete )
|
||||
{
|
||||
const char* frame = p + 2;
|
||||
while( *frame == ' ' ) frame++;
|
||||
std::string f( frame );
|
||||
while( !f.empty() && strchr( "\r\n", f.back() ) ) f.pop_back();
|
||||
if( !f.empty() ) pending_complete->frames.push_back( std::move( f ) );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Any non-frame line closes the pending zone before we process it.
|
||||
flushComplete();
|
||||
|
||||
// Only process lines whose first character is a digit.
|
||||
// Silently skips strace warnings, "Process N attached/detached", etc.
|
||||
if( !isdigit( (unsigned char)*p ) ) continue;
|
||||
|
||||
// Detect which line format strace used:
|
||||
// Format A (-o to file/pipe): "PID TIMESTAMP syscall..."
|
||||
// Format B (to stderr/tty): "TIMESTAMP [pid N] syscall..."
|
||||
// Distinguish by whether the first numeric token contains a '.' (timestamp)
|
||||
// or is followed by a space without a '.' (bare PID).
|
||||
uint64_t tid = kDefaultTid;
|
||||
{
|
||||
const char* q = p;
|
||||
while( isdigit( (unsigned char)*q ) ) q++;
|
||||
if( *q == ' ' )
|
||||
{
|
||||
// Format A: leading PID token
|
||||
while( isdigit( (unsigned char)*p ) ) tid = tid * 10 + (*p++ - '0');
|
||||
while( *p == ' ' ) p++;
|
||||
}
|
||||
// else: Format B — timestamp comes first, [pid N] (if any) follows it
|
||||
}
|
||||
|
||||
uint64_t ts = ParseTimestamp( p );
|
||||
if( ts == UINT64_MAX ) continue;
|
||||
|
||||
while( *p == ' ' ) p++;
|
||||
|
||||
// Format B may carry "[pid N]" after the timestamp; Format A already has tid.
|
||||
if( tid == kDefaultTid )
|
||||
tid = ConsumePidPrefix( p, kDefaultTid );
|
||||
|
||||
while( *p == ' ' ) p++;
|
||||
|
||||
// Register the thread name the first time we see this TID.
|
||||
if( threadNames.find( tid ) == threadNames.end() )
|
||||
{
|
||||
char name[32];
|
||||
if( tid == kDefaultTid )
|
||||
snprintf( name, sizeof( name ), "main" );
|
||||
else
|
||||
snprintf( name, sizeof( name ), "%" PRIu64, tid );
|
||||
threadNames[tid] = name;
|
||||
}
|
||||
|
||||
// --- Resumed syscall -------------------------------------------
|
||||
// "<... SYSCALL resumed>) = RETVAL <DUR>"
|
||||
if( strncmp( p, "<...", 4 ) == 0 )
|
||||
{
|
||||
const char* name_start = p + 4;
|
||||
while( *name_start == ' ' ) name_start++;
|
||||
const char* resumed = strstr( name_start, " resumed" );
|
||||
if( !resumed ) continue;
|
||||
|
||||
std::string syscall_name( name_start, resumed );
|
||||
|
||||
auto it = pending.find( tid );
|
||||
uint64_t ts_begin = ( it != pending.end() ) ? it->second.ts_begin : ts;
|
||||
std::string args_text = ( it != pending.end() ) ? it->second.args_text : "";
|
||||
if( it != pending.end() ) pending.erase( it );
|
||||
|
||||
// ts = timestamp of the return line; use as zone end.
|
||||
pending_complete = PendingComplete{ tid, ts_begin, ts,
|
||||
std::move( syscall_name ),
|
||||
std::move( args_text ), {} };
|
||||
}
|
||||
// --- Process exit / kill ----------------------------------------
|
||||
// "+++ exited with N +++" | "+++ killed by SIGNAL +++"
|
||||
else if( strncmp( p, "+++", 3 ) == 0 )
|
||||
{
|
||||
std::string raw( p );
|
||||
while( !raw.empty() && strchr( "\r\n", raw.back() ) ) raw.pop_back();
|
||||
messages.push_back( { tid, ts, std::to_string( tid ) + ": " + raw } );
|
||||
}
|
||||
// --- Signal ------------------------------------------------------
|
||||
// "--- SIGNAME {...} ---"
|
||||
else if( strncmp( p, "---", 3 ) == 0 )
|
||||
{
|
||||
std::string raw( p );
|
||||
while( !raw.empty() && strchr( "\r\n", raw.back() ) ) raw.pop_back();
|
||||
messages.push_back( { tid, ts, std::to_string( tid ) + ": " + raw } );
|
||||
}
|
||||
// --- Regular syscall (complete or unfinished) --------------------
|
||||
else
|
||||
{
|
||||
std::string syscall_name = ParseSyscallName( p );
|
||||
if( syscall_name.empty() ) continue;
|
||||
|
||||
if( strstr( p, "<unfinished ...>" ) )
|
||||
{
|
||||
// Syscall blocked — park in the pending table.
|
||||
std::string args = ExtractArgsUnfinished( p, " <unfinished" );
|
||||
pending[tid] = PendingEntry{ ts, std::move( syscall_name ), std::move( args ) };
|
||||
}
|
||||
else
|
||||
{
|
||||
// Complete call with return value (and optional duration from -T).
|
||||
uint64_t dur_ns = ParseTrailingDuration( buf );
|
||||
std::string args = ExtractArgsComplete( p );
|
||||
|
||||
// prctl(PR_SET_NAME, "name") — update the thread's display name.
|
||||
if( syscall_name == "prctl" && strstr( p, "PR_SET_NAME" ) )
|
||||
{
|
||||
std::string name = ExtractQuotedString( strstr( p, "PR_SET_NAME" ) );
|
||||
if( !name.empty() )
|
||||
threadNames[tid] = std::move( name );
|
||||
}
|
||||
// clone/clone3 with CLONE_THREAD — pre-name the child thread so it
|
||||
// has a label before it calls prctl itself.
|
||||
else if( ( syscall_name == "clone" || syscall_name == "clone3" ) &&
|
||||
strstr( p, "CLONE_THREAD" ) )
|
||||
{
|
||||
int64_t child_tid = ParseRetval( buf );
|
||||
if( child_tid > 0 && threadNames.find( (uint64_t)child_tid ) == threadNames.end() )
|
||||
{
|
||||
char name[32];
|
||||
snprintf( name, sizeof( name ), "%" PRId64, child_tid );
|
||||
threadNames[(uint64_t)child_tid] = name;
|
||||
}
|
||||
}
|
||||
|
||||
pending_complete = PendingComplete{ tid, ts, ts + dur_ns,
|
||||
syscall_name,
|
||||
std::move( args ), {} };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( fin != stdin ) fclose( fin );
|
||||
flushComplete(); // flush the last zone if the file ends with callstack frames
|
||||
|
||||
// Sort by timestamp. strace output is mostly ordered, but interleaved threads
|
||||
// and resumed events can place end-of-zone before begin-of-zone after sorting.
|
||||
std::stable_sort( timeline.begin(), timeline.end(),
|
||||
[]( const auto& a, const auto& b ) { return a.timestamp < b.timestamp; } );
|
||||
std::stable_sort( messages.begin(), messages.end(),
|
||||
[]( const auto& a, const auto& b ) { return a.timestamp < b.timestamp; } );
|
||||
|
||||
// Shift all timestamps so the trace starts at t = 0.
|
||||
uint64_t mts = UINT64_MAX;
|
||||
for( const auto& e : timeline ) mts = std::min( mts, e.timestamp );
|
||||
for( const auto& e : messages ) mts = std::min( mts, e.timestamp );
|
||||
if( mts == UINT64_MAX ) mts = 0;
|
||||
|
||||
for( auto& e : timeline ) e.timestamp -= mts;
|
||||
for( auto& e : messages ) e.timestamp -= mts;
|
||||
|
||||
const size_t zone_count = timeline.size() / 2;
|
||||
fprintf( stderr, "Parsed %zu zones, %zu messages, %zu threads\n",
|
||||
zone_count, messages.size(), threadNames.size() );
|
||||
|
||||
// Tracy Worker expects basenames, not full paths.
|
||||
auto basename = []( const char* path ) -> const char* {
|
||||
const char* s = path;
|
||||
for( const char* q = path; *q; q++ )
|
||||
if( *q == '/' || *q == '\\' ) s = q + 1;
|
||||
return s;
|
||||
};
|
||||
|
||||
tracy::Worker worker( basename( output_path ), basename( input_path ),
|
||||
timeline, messages, plots, threadNames );
|
||||
|
||||
auto w = std::unique_ptr<tracy::FileWrite>( tracy::FileWrite::Open( output_path, tracy::FileCompression::Fast ) );
|
||||
if( !w )
|
||||
{
|
||||
fprintf( stderr, "Cannot open output file: %s\n", output_path );
|
||||
return 1;
|
||||
}
|
||||
worker.Write( *w, false );
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user