Compare commits

...

18 Commits

Author SHA1 Message Date
Bartosz Taudul
8b86ada40e Fix checks. 2026-06-17 01:31:56 +02:00
Bartosz Taudul
616f33ff65 Consistent wait.time checks. 2026-06-17 01:31:01 +02:00
Bartosz Taudul
023cb20ba9 Only display wait reason or state if present. 2026-06-17 00:33:00 +02:00
Bartosz Taudul
073cd266ac Use proper thread id for wait stacks.
ContextSwitchData::Thread() is something related to fibers. Pass and use
known thread id from external context instead.
2026-06-17 00:21:51 +02:00
Bartosz Taudul
2e252d3988 Fix indentation level. 2026-06-17 00:14:03 +02:00
Bartosz Taudul
77978b68ca Explain wait stacks in callstack skill. 2026-06-16 23:53:41 +02:00
Bartosz Taudul
d9939362f5 Add wait stack data to the LLM attachment. 2026-06-16 23:53:40 +02:00
Bartosz Taudul
bba05bd3ad Update manual. 2026-06-16 23:33:16 +02:00
Bartosz Taudul
83a4a13cbd Display wait stack label and tooltip in callstack window. 2026-06-16 23:28:24 +02:00
Bartosz Taudul
9e9aabe9a1 Push CallstackViewWait to View::DrawCallstackTable(). 2026-06-16 23:18:40 +02:00
Bartosz Taudul
382a887ce9 Provide default values for View::DrawCallstackTable() parameters. 2026-06-16 23:17:47 +02:00
Bartosz Taudul
fbd1c55151 Extend View::ViewCallstack() to support optional wait stack data. 2026-06-16 23:10:43 +02:00
Bartosz Taudul
398bab8041 Store wait stack data in CallstackView. 2026-06-16 23:05:59 +02:00
Bartosz Taudul
69245e751b Cosmetics. 2026-06-16 23:05:58 +02:00
Bartosz Taudul
d571f2bd59 Fix integer trace parameter input field size. 2026-06-16 19:17:18 +02:00
Bartosz Taudul
781938317d Bump pugixml to 1.16. 2026-06-16 18:10:38 +02:00
Bartosz Taudul
f9365abe4f Do not use samplers in renderer.
Tracy requires some textures to have repeat wrapping mode set. The ImGui
implementation of samplers doesn't make it easy to achieve. Disalbe use
of samplers and rely on texture flags, as done originally.
2026-06-16 16:02:08 +02:00
Bartosz Taudul
3f91f35d59 Merge pull request #1403 from wolfpld/slomp/gl-feature-check
Add routine to check for GL features/extensions at run-time
2026-06-16 14:50:30 +02:00
13 changed files with 134 additions and 38 deletions

View 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

View File

@@ -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)

View File

@@ -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

View File

@@ -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}

View File

@@ -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.

View File

@@ -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
}
};
}

View File

@@ -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();

View File

@@ -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 );

View File

@@ -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 ) );

View File

@@ -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:

View File

@@ -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();

View File

@@ -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();
}
}

View File

@@ -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() )
{