mirror of
https://github.com/wolfpld/tracy.git
synced 2026-06-18 13:18:58 +00:00
Compare commits
2 Commits
master
...
slomp/stra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff70f87107 | ||
|
|
ebc23ac4f0 |
@@ -1,13 +0,0 @@
|
||||
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,7 +142,6 @@ 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
|
||||
@@ -272,7 +271,7 @@ if(NOT EMSCRIPTEN)
|
||||
CPMAddPackage(
|
||||
NAME pugixml
|
||||
GITHUB_REPOSITORY zeux/pugixml
|
||||
GIT_TAG v1.16
|
||||
GIT_TAG v1.15
|
||||
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-18 <https://github.com/wolfpld/tracy>
|
||||
2026-06-15 <https://github.com/wolfpld/tracy>
|
||||
|
||||
# Quick overview {#quick-overview .unnumbered}
|
||||
|
||||
@@ -2769,26 +2769,15 @@ Sometimes it is desired to change how the profiled application behaves during th
|
||||
|
||||
void Callback(void* data, uint32_t idx, int32_t val)
|
||||
|
||||
The `data` parameter will have the same value as was specified in the macro. The `idx` argument is an user-defined parameter index and `val` is the value set in the profiler user interface (in the connection information popup, see section [4.4.2](#connectionpopup)).
|
||||
The `data` parameter will have the same value as was specified in the macro. The `idx` argument is an user-defined parameter index and `val` is the value set in the profiler user interface.
|
||||
|
||||
To specify individual parameters, use the `TracyParameterSetup(idx, name, type, val)` macro. The `idx` value will be passed to the callback function for identification purposes (Tracy doesn't care what it's set to), `name` is the parameter label, displayed on the list of parameters, and `val` is the initial value. Finally, `type` determines how to interpret the `val` value, and can be selected from:
|
||||
|
||||
- `TracyParamTypeInt` -- `val` is an integer value, with the profiler UI displaying a numerical entry field.
|
||||
|
||||
- `TracyParamTypeBool` -- `val` is a boolean value, with a checkbox in the user interface.
|
||||
|
||||
- `TracyParamTypeTrigger` -- a * Trigger* button is displayed, for cases when you just want some action to happen. Repeats the initial `val` value provided to the setup function.
|
||||
To specify individual parameters, use the `TracyParameterSetup(idx, name, isBool, val)` macro. The `idx` value will be passed to the callback function for identification purposes (Tracy doesn't care what it's set to). `Name` is the parameter label, displayed on the list of parameters. Finally, `isBool` determines if `val` should be interpreted as a boolean value, or as an integer number.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Important**
|
||||
>
|
||||
> Usage of trace parameters makes profiling runs dependent on user interaction with the profiler, and thus it's not recommended to be employed if a consistent profiling environment is desired. Furthermore, interaction with the parameters is only possible in the graphical profiling application but not in the command line capture utility.
|
||||
|
||||
|
||||
-----
|
||||
|
||||
- Circle Dot icon
|
||||
|
||||
## Source contents callback
|
||||
|
||||
Tracy performs several data discovery attempts to show you the source file contents associated with the executed program, which is explained in more detail in chapter [5.16](#sourceview). However, sometimes the source files cannot be accessed without your help. For example, you may want to profile a script that is loaded by the game and which only resides in an archive accessible only by your program. Accordingly, Tracy allows inserting your own custom step at the end of the source discovery chain, with the `TracySourceCallbackRegister(callback, data)` macro, where `callback` is a function conforming to the following signature:
|
||||
@@ -3005,7 +2994,7 @@ You can use the *Save trace* button to save the current profile data to a fi
|
||||
|
||||
If frame image capture has been implemented (chapter [3.3.3](#frameimages)), a thumbnail of the last received frame image will be provided for reference.
|
||||
|
||||
Suppose the profiled application opted to provide trace parameters (see section [3.18](#traceparameters)) and the connection is still active. In that case, this pop-up will also contain a *Trace parameters* section, listing all the provided options. A callback function will be executed on the client when you change any value here.
|
||||
Suppose the profiled application opted to provide trace parameters (see section [3.18](#traceparameters)) and the connection is still active. In that case, this pop-up will also contain a *trace parameters* section, listing all the provided options. A callback function will be executed on the client when you change any value here.
|
||||
|
||||
|
||||
-----
|
||||
@@ -4312,8 +4301,6 @@ 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.
|
||||
@@ -4343,8 +4330,6 @@ 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
|
||||
|
||||
@@ -3109,15 +3109,9 @@ Sometimes it is desired to change how the profiled application behaves during th
|
||||
void Callback(void* data, uint32_t idx, int32_t val)
|
||||
\end{lstlisting}
|
||||
|
||||
The \texttt{data} parameter will have the same value as was specified in the macro. The \texttt{idx} argument is an user-defined parameter index and \texttt{val} is the value set in the profiler user interface (in the connection information popup, see section~\ref{connectionpopup}).
|
||||
The \texttt{data} parameter will have the same value as was specified in the macro. The \texttt{idx} argument is an user-defined parameter index and \texttt{val} is the value set in the profiler user interface.
|
||||
|
||||
To specify individual parameters, use the \texttt{TracyParameterSetup(idx, name, type, val)} macro. The \texttt{idx} value will be passed to the callback function for identification purposes (Tracy doesn't care what it's set to), \texttt{name} is the parameter label, displayed on the list of parameters, and \texttt{val} is the initial value. Finally, \texttt{type} determines how to interpret the \texttt{val} value, and can be selected from:
|
||||
|
||||
\begin{itemize}
|
||||
\item \texttt{TracyParamTypeInt} -- \texttt{val} is an integer value, with the profiler UI displaying a numerical entry field.
|
||||
\item \texttt{TracyParamTypeBool} -- \texttt{val} is a boolean value, with a checkbox in the user interface.
|
||||
\item \texttt{TracyParamTypeTrigger} -- a~\emph{\faCircleDot{}~Trigger} button is displayed, for cases when you just want some action to happen. Repeats the initial \texttt{val} value provided to the setup function.
|
||||
\end{itemize}
|
||||
To specify individual parameters, use the \texttt{TracyParameterSetup(idx, name, isBool, val)} macro. The \texttt{idx} value will be passed to the callback function for identification purposes (Tracy doesn't care what it's set to). \texttt{Name} is the parameter label, displayed on the list of parameters. Finally, \texttt{isBool} determines if \texttt{val} should be interpreted as a boolean value, or as an integer number.
|
||||
|
||||
\begin{bclogo}[
|
||||
noborder=true,
|
||||
@@ -3341,7 +3335,7 @@ You can use the \faFloppyDisk{}~\emph{Save trace} button to save the current pro
|
||||
|
||||
If frame image capture has been implemented (chapter~\ref{frameimages}), a thumbnail of the last received frame image will be provided for reference.
|
||||
|
||||
Suppose the profiled application opted to provide trace parameters (see section~\ref{traceparameters}) and the connection is still active. In that case, this pop-up will also contain a \emph{Trace parameters} section, listing all the provided options. A callback function will be executed on the client when you change any value here.
|
||||
Suppose the profiled application opted to provide trace parameters (see section~\ref{traceparameters}) and the connection is still active. In that case, this pop-up will also contain a \emph{trace parameters} section, listing all the provided options. A callback function will be executed on the client when you change any value here.
|
||||
|
||||
\subsubsection{Automatic loading or connecting}
|
||||
|
||||
@@ -4690,8 +4684,6 @@ 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,12 +103,6 @@ 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.
|
||||
|
||||
@@ -177,8 +177,8 @@ static void SetupDPIScale()
|
||||
auto& style = ImGui::GetStyle();
|
||||
style = ImGuiStyle();
|
||||
ImGui::StyleColorsDark();
|
||||
style.WindowBorderSize = 1.f;
|
||||
style.FrameBorderSize = 1.f;
|
||||
style.WindowBorderSize = 1.f * scale;
|
||||
style.FrameBorderSize = 1.f * scale;
|
||||
style.FrameRounding = 5.f;
|
||||
style.Colors[ImGuiCol_ScrollbarBg] = ImVec4( 1, 1, 1, 0.03f );
|
||||
style.Colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f);
|
||||
|
||||
@@ -249,26 +249,6 @@ void View::ViewSymbol( const char* fileName, int line, uint64_t baseAddr, uint64
|
||||
m_sourceView->OpenSymbol( fileName, line, baseAddr, symAddr, m_worker, *this );
|
||||
}
|
||||
|
||||
void View::UpdateThreadOrder()
|
||||
{
|
||||
const auto& threadData = m_worker.GetThreadData();
|
||||
if( threadData.size() == m_threadOrder.size() ) return;
|
||||
|
||||
m_threadOrder.reserve( threadData.size() );
|
||||
// Only new threads are in the end of the worker's ThreadData vector.
|
||||
// Threads which get reordered by received thread hints are not new, yet removed from m_threadOrder.
|
||||
// Therefore, those are kept in m_threadReinsert and are gathered before the remaining new threads.
|
||||
const size_t numReinsert = m_threadReinsert.size();
|
||||
const size_t numNew = threadData.size() - m_threadOrder.size() - numReinsert;
|
||||
for( size_t i = 0; i < numReinsert + numNew; i++ )
|
||||
{
|
||||
const ThreadData* td = i < numReinsert ? m_threadReinsert[i] : threadData[m_threadOrder.size()];
|
||||
auto it = std::find_if( m_threadOrder.begin(), m_threadOrder.end(), [td]( const auto t ) { return td->groupHint < t->groupHint; } );
|
||||
m_threadOrder.insert( it, td );
|
||||
}
|
||||
m_threadReinsert.clear();
|
||||
}
|
||||
|
||||
bool View::ViewDispatch( const char* fileName, int line, uint64_t symAddr )
|
||||
{
|
||||
if( line == 0 )
|
||||
@@ -1551,18 +1531,11 @@ void View::AddLlmQuery( const char* query )
|
||||
#endif
|
||||
}
|
||||
|
||||
void View::ViewCallstack( uint32_t callstack, uint32_t thread, int64_t waitTime, const char* waitReason, const char* waitReasonCode, const char* waitState, const char* waitStateCode )
|
||||
void View::ViewCallstack( uint32_t callstack, uint32_t thread )
|
||||
{
|
||||
m_callstackView = {
|
||||
.id = callstack,
|
||||
.thread = thread,
|
||||
.wait = {
|
||||
.time = waitTime,
|
||||
.reason = waitReason,
|
||||
.reasonCode = waitReasonCode,
|
||||
.state = waitState,
|
||||
.stateCode = waitStateCode
|
||||
}
|
||||
.thread = thread
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ public:
|
||||
void AddLlmAttachment( const nlohmann::json& json );
|
||||
void AddLlmQuery( const char* query );
|
||||
|
||||
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 );
|
||||
void ViewCallstack( uint32_t callstack, uint32_t thread );
|
||||
|
||||
nlohmann::json GetCallstackJson( const CallstackFrameId* data, size_t size ) const;
|
||||
|
||||
@@ -261,20 +261,10 @@ 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();
|
||||
@@ -293,7 +283,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, uint64_t tid );
|
||||
void DrawContextSwitchList( const TimelineContext& ctx, const std::vector<ContextSwitchDraw>& drawList, const Vector<ContextSwitchData>& ctxSwitch, int offset, int endOffset, bool isFiber );
|
||||
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 );
|
||||
@@ -314,8 +304,8 @@ private:
|
||||
void DrawAllocList();
|
||||
void DrawCompare();
|
||||
void DrawCallstackWindow();
|
||||
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 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 DrawMemoryAllocWindow();
|
||||
void DrawInfo();
|
||||
void DrawTextEditor();
|
||||
@@ -447,7 +437,6 @@ private:
|
||||
void UpdateTitle();
|
||||
|
||||
void ValidateSourceRegex();
|
||||
void UpdateThreadOrder();
|
||||
|
||||
unordered_flat_map<uint64_t, int> m_threadDepthLimit;
|
||||
unordered_flat_map<uint64_t, bool> m_visibleMsgThread;
|
||||
|
||||
@@ -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, m_callstackView.wait, true, true );
|
||||
DrawCallstackTable( m_callstackView.id, m_callstackView.thread, true, true );
|
||||
}
|
||||
ImGui::End();
|
||||
if( !show ) m_callstackView = {};
|
||||
}
|
||||
|
||||
void View::DrawCallstackTable( uint32_t callstack, uint64_t thread, const CallstackViewWait& wait, bool globalEntriesButton, bool showThread )
|
||||
void View::DrawCallstackTable( uint32_t callstack, uint64_t thread, 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, wait, globalEntriesButton, showThread, hasCrashed, callstack );
|
||||
DrawCallstackTable( cs.data(), cs.size(), thread, globalEntriesButton, showThread, hasCrashed, 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 )
|
||||
void View::DrawCallstackTable( const CallstackFrameId* data, size_t size, uint64_t thread, 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, wait, hasCrashed, thread, callstack]() {
|
||||
auto Attach = [this, data, size, hasCrashed, thread, callstack]() {
|
||||
auto json = GetCallstackJson( data, size );
|
||||
if( hasCrashed )
|
||||
{
|
||||
@@ -131,14 +131,6 @@ 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 );
|
||||
};
|
||||
@@ -209,32 +201,6 @@ 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 );
|
||||
|
||||
@@ -203,33 +203,21 @@ bool View::DrawConnection()
|
||||
ImGui::TextUnformatted( m_worker.GetString( p.name ) );
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID( idx );
|
||||
switch( p.type )
|
||||
{
|
||||
case ParameterType::Boolean:
|
||||
if( p.isBool )
|
||||
{
|
||||
bool val = p.val;
|
||||
if( ImGui::Checkbox( "", &val ) )
|
||||
{
|
||||
m_worker.SetParameter( idx, int32_t( val ) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ParameterType::Integer:
|
||||
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 ) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ParameterType::Trigger:
|
||||
if( ImGui::Button( ICON_FA_CIRCLE_DOT ) )
|
||||
{
|
||||
m_worker.SetParameter( idx, p.val );
|
||||
}
|
||||
break;
|
||||
}
|
||||
ImGui::PopID();
|
||||
idx++;
|
||||
|
||||
@@ -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, uint64_t tid )
|
||||
void View::DrawContextSwitchList( const TimelineContext& ctx, const std::vector<ContextSwitchDraw>& drawList, const Vector<ContextSwitchData>& ctxSwitch, int offset, int endOffset, bool isFiber )
|
||||
{
|
||||
constexpr float MinCtxSize = 4;
|
||||
|
||||
@@ -210,12 +210,6 @@ 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 ) ) )
|
||||
{
|
||||
@@ -227,9 +221,8 @@ 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( waitTime ) );
|
||||
TextFocused( "Waiting time:", TimeToString( ev.WakeupVal() - prev.End() ) );
|
||||
if( migration )
|
||||
{
|
||||
TextFocused( "CPU:", RealToString( prev.Cpu() ) );
|
||||
@@ -242,22 +235,18 @@ void View::DrawContextSwitchList( const TimelineContext& ctx, const std::vector<
|
||||
}
|
||||
if( prev.Reason() != 100 )
|
||||
{
|
||||
waitReason = DecodeContextSwitchReason( prev.Reason() );
|
||||
waitReasonCode = DecodeContextSwitchReasonCode( prev.Reason() );
|
||||
TextFocused( "Wait reason:", waitReasonCode );
|
||||
TextFocused( "Wait reason:", DecodeContextSwitchReasonCode( prev.Reason() ) );
|
||||
ImGui::SameLine();
|
||||
ImGui::PushFont( g_fonts.normal, FontSmall );
|
||||
ImGui::AlignTextToFramePadding();
|
||||
TextDisabledUnformatted( waitReason );
|
||||
TextDisabledUnformatted( DecodeContextSwitchReason( prev.Reason() ) );
|
||||
ImGui::PopFont();
|
||||
}
|
||||
waitState = DecodeContextSwitchState( prev.State() );
|
||||
waitStateCode = DecodeContextSwitchStateCode( prev.State() );
|
||||
TextFocused( "Wait state:", waitStateCode );
|
||||
TextFocused( "Wait state:", DecodeContextSwitchStateCode( prev.State() ) );
|
||||
ImGui::SameLine();
|
||||
ImGui::PushFont( g_fonts.normal, FontSmall );
|
||||
ImGui::AlignTextToFramePadding();
|
||||
TextDisabledUnformatted( waitState );
|
||||
TextDisabledUnformatted( DecodeContextSwitchState( prev.State() ) );
|
||||
ImGui::PopFont();
|
||||
}
|
||||
tooltip = true;
|
||||
@@ -286,23 +275,16 @@ 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 = tid,
|
||||
.wait = {
|
||||
.time = waitTime,
|
||||
.reason = waitReason,
|
||||
.reasonCode = waitReasonCode,
|
||||
.state = waitState,
|
||||
.stateCode = waitStateCode
|
||||
}
|
||||
};
|
||||
}
|
||||
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::EndTooltip();
|
||||
}
|
||||
@@ -407,8 +389,6 @@ void View::DrawContextSwitchList( const TimelineContext& ctx, const std::vector<
|
||||
|
||||
void View::DrawWaitStacks()
|
||||
{
|
||||
UpdateThreadOrder();
|
||||
|
||||
const auto scale = GetScale();
|
||||
ImGui::SetNextWindowSize( ImVec2( 1400 * scale, 500 * scale ), ImGuiCond_FirstUseEver );
|
||||
ImGui::Begin( "Wait stacks", &m_showWaitStacks );
|
||||
@@ -636,7 +616,7 @@ void View::DrawWaitStacks()
|
||||
PrintStringPercent( buf, 100. * data[m_waitStack]->second / totalCount );
|
||||
TextDisabledUnformatted( buf );
|
||||
ImGui::Separator();
|
||||
DrawCallstackTable( data[m_waitStack]->first );
|
||||
DrawCallstackTable( data[m_waitStack]->first, 0, false, false );
|
||||
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();
|
||||
|
||||
@@ -1026,8 +1026,7 @@ void View::DrawFlameGraph()
|
||||
m_flameGraphPan = 0;
|
||||
}
|
||||
|
||||
UpdateThreadOrder();
|
||||
const auto& td = m_threadOrder;
|
||||
auto& td = m_worker.GetThreadData();
|
||||
auto expand = ImGui::TreeNode( ICON_FA_SHUFFLE " Visible threads:" );
|
||||
ImGui::SameLine();
|
||||
size_t visibleThreads = 0;
|
||||
|
||||
@@ -137,8 +137,6 @@ void View::DrawMessages()
|
||||
ImGui::Checkbox( ICON_FA_IMAGE " Show frame images", &m_showMessageImages );
|
||||
}
|
||||
|
||||
UpdateThreadOrder();
|
||||
|
||||
bool threadsChanged = false;
|
||||
ImGui::AlignTextToFramePadding();
|
||||
auto expand = ImGui::TreeNodeEx( ICON_FA_SHUFFLE " Visible threads:", ImGuiTreeNodeFlags_SpanLabelWidth );
|
||||
|
||||
@@ -393,7 +393,24 @@ void View::DrawTimeline()
|
||||
}
|
||||
if( m_vd.drawZones )
|
||||
{
|
||||
UpdateThreadOrder();
|
||||
const auto& threadData = m_worker.GetThreadData();
|
||||
if( threadData.size() != m_threadOrder.size() )
|
||||
{
|
||||
m_threadOrder.reserve( threadData.size() );
|
||||
// Only new threads are in the end of the worker's ThreadData vector.
|
||||
// Threads which get reordered by received thread hints are not new, yet removed from m_threadOrder.
|
||||
// Therefore, those are kept in the m_threadReinsert vector. As such, we will gather first threads from the
|
||||
// reinsert vector, and afterwards the remaining ones must be new (and thus found at the end of threadData).
|
||||
size_t numReinsert = m_threadReinsert.size();
|
||||
size_t numNew = threadData.size() - m_threadOrder.size() - numReinsert;
|
||||
for( size_t i = 0; i < numReinsert + numNew; i++ )
|
||||
{
|
||||
const ThreadData *td = i < numReinsert ? m_threadReinsert[i] : threadData[m_threadOrder.size()];
|
||||
auto it = std::find_if( m_threadOrder.begin(), m_threadOrder.end(), [td]( const auto t ) { return td->groupHint < t->groupHint; } );
|
||||
m_threadOrder.insert( it, td );
|
||||
}
|
||||
m_threadReinsert.clear();
|
||||
}
|
||||
for( const auto& v : m_threadOrder )
|
||||
{
|
||||
m_tc.AddItem<TimelineItemThread>( v );
|
||||
|
||||
@@ -1077,7 +1077,7 @@ void View::DrawZoneInfoWindow()
|
||||
{
|
||||
if( ImGui::TreeNode( "Call stack" ) )
|
||||
{
|
||||
DrawCallstackTable( m_worker.GetZoneExtra( ev ).callstack.Val(), tid );
|
||||
DrawCallstackTable( m_worker.GetZoneExtra( ev ).callstack.Val(), tid, false, false );
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
@@ -1091,7 +1091,7 @@ void View::DrawZoneInfoWindow()
|
||||
TextDisabledUnformatted( ICON_FA_WAND_SPARKLES );
|
||||
if( expand )
|
||||
{
|
||||
DrawCallstackTable( cs.data(), cs.size(), tid );
|
||||
DrawCallstackTable( cs.data(), cs.size(), tid, false, false );
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
@@ -1568,7 +1568,7 @@ void View::DrawGpuInfoWindow()
|
||||
{
|
||||
if( ImGui::TreeNode( "Call stack" ) )
|
||||
{
|
||||
DrawCallstackTable( ev.callstack.Val(), tid );
|
||||
DrawCallstackTable( ev.callstack.Val(), tid, false, false );
|
||||
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, thread.id );
|
||||
DrawContextSwitchList( ctx, ctxDraw, ctxSwitch->v, ctxOffset, offset, thread.isFiber );
|
||||
}
|
||||
if( hasSamples && !samplesDraw.empty() )
|
||||
{
|
||||
|
||||
@@ -1521,7 +1521,6 @@ Profiler::Profiler()
|
||||
, m_noExit( false )
|
||||
, m_userPort( 0 )
|
||||
, m_zoneId( 1 )
|
||||
, m_sectionId( 1 )
|
||||
, m_samplingPeriod( 0 )
|
||||
, m_stream( LZ4_createStream() )
|
||||
, m_buffer( (char*)tracy_malloc( TargetFrameSize*3 ) )
|
||||
@@ -2485,10 +2484,6 @@ static void FreeAssociatedMemory( const QueueItem& item )
|
||||
ptr = MemRead( &item.sourceCodeMetadata.ptr );
|
||||
tracy_free( (void*)ptr );
|
||||
break;
|
||||
case QueueType::SectionEnter:
|
||||
ptr = MemRead( &item.sectionEnterFat.text );
|
||||
tracy_free( (void*)ptr );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -2868,26 +2863,6 @@ Profiler::DequeueStatus Profiler::Dequeue( moodycamel::ConsumerToken& token )
|
||||
++item;
|
||||
continue;
|
||||
}
|
||||
case QueueType::SectionEnter:
|
||||
{
|
||||
int64_t t = MemRead( &item->sectionEnter.time );
|
||||
int64_t dt = t - refThread;
|
||||
refThread = t;
|
||||
MemWrite( &item->sectionEnter.time, dt );
|
||||
ptr = MemRead( &item->sectionEnterFat.text );
|
||||
size = MemRead( &item->sectionEnterFat.size );
|
||||
SendSingleString( (const char*)ptr, size );
|
||||
tracy_free_fast( (void*)ptr );
|
||||
break;
|
||||
}
|
||||
case QueueType::SectionLeave:
|
||||
{
|
||||
int64_t t = MemRead( &item->sectionLeave.time );
|
||||
int64_t dt = t - refThread;
|
||||
refThread = t;
|
||||
MemWrite( &item->sectionLeave.time, dt );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert( false );
|
||||
break;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <assert.h>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
@@ -20,7 +19,6 @@
|
||||
#include "../common/TracyQueue.hpp"
|
||||
#include "../common/TracyAlign.hpp"
|
||||
#include "../common/TracyAlloc.hpp"
|
||||
#include "../common/TracyFormat.hpp"
|
||||
#include "../common/TracyMutex.hpp"
|
||||
#include "../common/TracyProtocol.hpp"
|
||||
|
||||
@@ -748,13 +746,12 @@ public:
|
||||
profiler.m_paramCallbackData = data;
|
||||
}
|
||||
|
||||
static tracy_force_inline void ParameterSetup( uint32_t idx, const char* name, uint8_t type, int32_t val )
|
||||
static tracy_force_inline void ParameterSetup( uint32_t idx, const char* name, bool isBool, int32_t val )
|
||||
{
|
||||
assert( type >= 0 && type <= 2 );
|
||||
TracyLfqPrepare( QueueType::ParamSetup );
|
||||
tracy::MemWrite( &item->paramSetup.idx, idx );
|
||||
tracy::MemWrite( &item->paramSetup.name, (uint64_t)name );
|
||||
tracy::MemWrite( &item->paramSetup.type, type );
|
||||
tracy::MemWrite( &item->paramSetup.isBool, (uint8_t)isBool );
|
||||
tracy::MemWrite( &item->paramSetup.val, val );
|
||||
|
||||
#ifdef TRACY_ON_DEMAND
|
||||
@@ -795,46 +792,6 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
static tracy_force_inline uint32_t SectionEnter( const char* fmt, ... ) TRACY_ATTRIBUTE_FORMAT_PRINTF( 1, 2 )
|
||||
{
|
||||
auto& profiler = GetProfiler();
|
||||
#ifdef TRACY_ON_DEMAND
|
||||
if( !profiler.IsConnected() ) return 0;
|
||||
#endif
|
||||
va_list args;
|
||||
va_start( args, fmt );
|
||||
auto size = vsnprintf( nullptr, 0, fmt, args );
|
||||
va_end( args );
|
||||
if( size < 0 ) return 0;
|
||||
assert( size < (std::numeric_limits<uint16_t>::max)() );
|
||||
|
||||
char* ptr = (char*)tracy_malloc( size_t( size ) + 1 );
|
||||
va_start( args, fmt );
|
||||
vsnprintf( ptr, size_t( size ) + 1, fmt, args );
|
||||
va_end( args );
|
||||
|
||||
const auto id = profiler.m_sectionId.fetch_add( 1, std::memory_order_relaxed );
|
||||
TracyLfqPrepare( QueueType::SectionEnter );
|
||||
MemWrite( &item->sectionEnterFat.time, GetTime() );
|
||||
MemWrite( &item->sectionEnterFat.id, id );
|
||||
MemWrite( &item->sectionEnterFat.text, (uint64_t)ptr );
|
||||
MemWrite( &item->sectionEnterFat.size, (uint16_t)size );
|
||||
TracyLfqCommit;
|
||||
return id;
|
||||
}
|
||||
|
||||
static tracy_force_inline void SectionLeave( uint32_t id )
|
||||
{
|
||||
if( id == 0 ) return;
|
||||
#ifdef TRACY_ON_DEMAND
|
||||
if( !GetProfiler().IsConnected() ) return;
|
||||
#endif
|
||||
TracyLfqPrepare( QueueType::SectionLeave );
|
||||
MemWrite( &item->sectionLeave.time, GetTime() );
|
||||
MemWrite( &item->sectionLeave.id, id );
|
||||
TracyLfqCommit;
|
||||
}
|
||||
|
||||
void SendCallstack( int32_t depth, const char** skipBefore );
|
||||
static void CutCallstack( void* callstack, const char** skipBefore );
|
||||
|
||||
@@ -1107,7 +1064,6 @@ private:
|
||||
bool m_noExit;
|
||||
uint32_t m_userPort;
|
||||
std::atomic<uint32_t> m_zoneId;
|
||||
std::atomic<uint32_t> m_sectionId;
|
||||
int64_t m_samplingPeriod;
|
||||
|
||||
uint32_t m_threadCtx;
|
||||
|
||||
@@ -9,10 +9,15 @@
|
||||
#include "../common/TracySystem.hpp"
|
||||
#include "../common/TracyAlign.hpp"
|
||||
#include "../common/TracyAlloc.hpp"
|
||||
#include "../common/TracyFormat.hpp"
|
||||
#include "TracyProfiler.hpp"
|
||||
#include "TracyCallstack.hpp"
|
||||
|
||||
#if (defined(__GNUC__) || defined(__clang__))
|
||||
# define TRACY_ATTRIBUTE_FORMAT_PRINTF(fmt_idx, arg_idx) \
|
||||
__attribute__((format(printf, fmt_idx, arg_idx)))
|
||||
#else
|
||||
# define TRACY_ATTRIBUTE_FORMAT_PRINTF(fmt_idx, arg_idx)
|
||||
#endif
|
||||
namespace tracy
|
||||
{
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#ifndef __TRACYFORMAT_HPP__
|
||||
#define __TRACYFORMAT_HPP__
|
||||
|
||||
#if (defined(__GNUC__) || defined(__clang__))
|
||||
# define TRACY_ATTRIBUTE_FORMAT_PRINTF(fmt_idx, arg_idx) \
|
||||
__attribute__((format(printf, fmt_idx, arg_idx)))
|
||||
#else
|
||||
# define TRACY_ATTRIBUTE_FORMAT_PRINTF(fmt_idx, arg_idx)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -10,7 +10,7 @@ namespace tracy
|
||||
|
||||
constexpr unsigned Lz4CompressBound( unsigned isize ) { return isize + ( isize / 255 ) + 16; }
|
||||
|
||||
constexpr uint32_t ProtocolVersion = 81;
|
||||
constexpr uint32_t ProtocolVersion = 80;
|
||||
constexpr uint16_t BroadcastVersion = 3;
|
||||
|
||||
using lz4sz_t = uint32_t;
|
||||
|
||||
@@ -81,8 +81,6 @@ enum class QueueType : uint8_t
|
||||
SourceCodeMetadata,
|
||||
FiberEnter,
|
||||
FiberLeave,
|
||||
SectionEnter,
|
||||
SectionLeave,
|
||||
Terminate,
|
||||
KeepAlive,
|
||||
ThreadContext,
|
||||
@@ -311,24 +309,6 @@ struct QueueFiberLeave
|
||||
uint32_t thread;
|
||||
};
|
||||
|
||||
struct QueueSectionEnter
|
||||
{
|
||||
int64_t time;
|
||||
uint32_t id;
|
||||
};
|
||||
|
||||
struct QueueSectionEnterFat : public QueueSectionEnter
|
||||
{
|
||||
uint64_t text; // ptr
|
||||
uint16_t size;
|
||||
};
|
||||
|
||||
struct QueueSectionLeave
|
||||
{
|
||||
int64_t time;
|
||||
uint32_t id;
|
||||
};
|
||||
|
||||
struct QueueLockTerminate
|
||||
{
|
||||
uint32_t id;
|
||||
@@ -795,7 +775,7 @@ struct QueueParamSetup
|
||||
{
|
||||
uint32_t idx;
|
||||
uint64_t name; // ptr
|
||||
uint8_t type;
|
||||
uint8_t isBool;
|
||||
int32_t val;
|
||||
};
|
||||
|
||||
@@ -941,9 +921,6 @@ struct QueueItem
|
||||
QueueSourceCodeNotAvailable sourceCodeNotAvailable;
|
||||
QueueFiberEnter fiberEnter;
|
||||
QueueFiberLeave fiberLeave;
|
||||
QueueSectionEnter sectionEnter;
|
||||
QueueSectionEnterFat sectionEnterFat;
|
||||
QueueSectionLeave sectionLeave;
|
||||
QueueGpuZoneAnnotation zoneAnnotation;
|
||||
};
|
||||
};
|
||||
@@ -1023,8 +1000,6 @@ static constexpr size_t QueueDataSize[] = {
|
||||
sizeof( QueueHeader ), // SourceCodeMetadata - not for wire transfer
|
||||
sizeof( QueueHeader ) + sizeof( QueueFiberEnter ),
|
||||
sizeof( QueueHeader ) + sizeof( QueueFiberLeave ),
|
||||
sizeof( QueueHeader ) + sizeof( QueueSectionEnter ),
|
||||
sizeof( QueueHeader ) + sizeof( QueueSectionLeave ),
|
||||
// above items must be first
|
||||
sizeof( QueueHeader ), // terminate
|
||||
sizeof( QueueHeader ), // keep alive
|
||||
|
||||
@@ -16,12 +16,6 @@
|
||||
# define TracyLine TracyConcat(__LINE__,U) // MSVC Edit and continue __LINE__ is non-constant. See https://developercommunity.visualstudio.com/t/-line-cannot-be-used-as-an-argument-for-constexpr/195665
|
||||
#endif
|
||||
|
||||
|
||||
#define TracyParamTypeInt 0
|
||||
#define TracyParamTypeBool 1
|
||||
#define TracyParamTypeTrigger 2
|
||||
|
||||
|
||||
#ifndef TRACY_ENABLE
|
||||
|
||||
#define TracyNoop
|
||||
@@ -118,9 +112,6 @@
|
||||
#define TracyIsStarted false
|
||||
#define TracySetProgramName(x)
|
||||
|
||||
#define TracySectionEnter(x, ...) 0;
|
||||
#define TracySectionLeave(x)
|
||||
|
||||
#define TracyFiberEnter(x)
|
||||
#define TracyFiberEnterHint(x,y)
|
||||
#define TracyFiberLeave
|
||||
@@ -254,13 +245,10 @@
|
||||
|
||||
#define TracySourceCallbackRegister( cb, data ) tracy::Profiler::SourceCallbackRegister( cb, data )
|
||||
#define TracyParameterRegister( cb, data ) tracy::Profiler::ParameterRegister( cb, data )
|
||||
#define TracyParameterSetup( idx, name, type, val ) tracy::Profiler::ParameterSetup( idx, name, type, val )
|
||||
#define TracyParameterSetup( idx, name, isBool, val ) tracy::Profiler::ParameterSetup( idx, name, isBool, val )
|
||||
#define TracyIsConnected tracy::GetProfiler().IsConnected()
|
||||
#define TracySetProgramName( name ) tracy::GetProfiler().SetProgramName( name );
|
||||
|
||||
#define TracySectionEnter( fmt, ... ) tracy::Profiler::SectionEnter( fmt, ##__VA_ARGS__ );
|
||||
#define TracySectionLeave( id ) tracy::Profiler::SectionLeave( id );
|
||||
|
||||
#ifdef TRACY_FIBERS
|
||||
# define TracyFiberEnter( fiber ) tracy::Profiler::EnterFiber( fiber, 0 )
|
||||
# define TracyFiberEnterHint( fiber, groupHint ) tracy::Profiler::EnterFiber( fiber, groupHint )
|
||||
|
||||
@@ -107,12 +107,6 @@ 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 )
|
||||
@@ -215,30 +209,6 @@ 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.
|
||||
|
||||
@@ -683,14 +683,6 @@ struct ChildSample
|
||||
uint64_t addr;
|
||||
};
|
||||
|
||||
|
||||
struct SectionItem
|
||||
{
|
||||
Int48 start, end;
|
||||
StringIdx text;
|
||||
uint32_t id;
|
||||
};
|
||||
|
||||
#pragma pack( pop )
|
||||
|
||||
|
||||
@@ -857,18 +849,11 @@ struct CpuThreadData
|
||||
};
|
||||
|
||||
|
||||
enum class ParameterType
|
||||
{
|
||||
Integer,
|
||||
Boolean,
|
||||
Trigger
|
||||
};
|
||||
|
||||
struct Parameter
|
||||
{
|
||||
uint32_t idx;
|
||||
StringRef name;
|
||||
ParameterType type;
|
||||
bool isBool;
|
||||
int32_t val;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
# if __has_include(<charconv>) && __has_include(<type_traits>)
|
||||
# include <charconv>
|
||||
# include <type_traits>
|
||||
# if !defined(__cpp_lib_to_chars)
|
||||
# define NO_CHARCONV
|
||||
# endif
|
||||
# else
|
||||
# define NO_CHARCONV
|
||||
# endif
|
||||
|
||||
@@ -4857,12 +4857,6 @@ bool Worker::Process( const QueueItem& ev )
|
||||
case QueueType::FiberLeave:
|
||||
ProcessFiberLeave( ev.fiberLeave );
|
||||
break;
|
||||
case QueueType::SectionEnter:
|
||||
ProcessSectionEnter( ev.sectionEnter );
|
||||
break;
|
||||
case QueueType::SectionLeave:
|
||||
ProcessSectionLeave( ev.sectionLeave );
|
||||
break;
|
||||
default:
|
||||
assert( false );
|
||||
break;
|
||||
@@ -7261,7 +7255,7 @@ void Worker::ProcessHwSampleBranchMiss( const QueueHwSample& ev )
|
||||
void Worker::ProcessParamSetup( const QueueParamSetup& ev )
|
||||
{
|
||||
CheckString( ev.name );
|
||||
m_params.push_back( Parameter { ev.idx, StringRef( StringRef::Ptr, ev.name ), ParameterType( ev.type ), ev.val } );
|
||||
m_params.push_back( Parameter { ev.idx, StringRef( StringRef::Ptr, ev.name ), bool( ev.isBool ), ev.val } );
|
||||
}
|
||||
|
||||
void Worker::ProcessSourceCodeNotAvailable( const QueueSourceCodeNotAvailable& ev )
|
||||
@@ -7376,63 +7370,6 @@ void Worker::ProcessFiberLeave( const QueueFiberLeave& ev )
|
||||
td->fiber = nullptr;
|
||||
}
|
||||
|
||||
void Worker::ProcessSectionEnter( const QueueSectionEnter& ev )
|
||||
{
|
||||
const auto t = TscTime( RefTime( m_refTimeThread, ev.time ) );
|
||||
if( m_data.lastTime < t ) m_data.lastTime = t;
|
||||
const auto text = GetSingleStringIdx();
|
||||
|
||||
const auto ait = m_data.sectionsActive.find( ev.id );
|
||||
if( ait != m_data.sectionsActive.end() )
|
||||
{
|
||||
m_data.sectionsActive.erase( ait );
|
||||
auto it = std::ranges::find_if( m_data.sectionsPending, [id = ev.id]( const auto& s ) { return s.id == id; } );
|
||||
assert( it != m_data.sectionsPending.end() );
|
||||
assert( it->start.Val() < 0 );
|
||||
assert( !it->text.Active() );
|
||||
it->start.SetVal( t );
|
||||
it->text.SetIdx( text );
|
||||
m_data.sections.push_back( *it );
|
||||
m_data.sectionsPending.erase( it );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data.sections.push_back( SectionItem {
|
||||
.start = t,
|
||||
.end = -1,
|
||||
.text = StringIdx( text ),
|
||||
.id = ev.id
|
||||
} );
|
||||
m_data.sectionsActive.insert( ev.id );
|
||||
}
|
||||
}
|
||||
|
||||
void Worker::ProcessSectionLeave( const QueueSectionLeave& ev )
|
||||
{
|
||||
const auto t = TscTime( RefTime( m_refTimeThread, ev.time ) );
|
||||
if( m_data.lastTime < t ) m_data.lastTime = t;
|
||||
|
||||
const auto ait = m_data.sectionsActive.find( ev.id );
|
||||
if( ait != m_data.sectionsActive.end() )
|
||||
{
|
||||
m_data.sectionsActive.erase( ait );
|
||||
auto it = std::ranges::find_if( m_data.sections, [id = ev.id]( const auto& s ) { return s.id == id; } );
|
||||
assert( it != m_data.sections.end() );
|
||||
assert( it->end.Val() < 0 );
|
||||
it->end.SetVal( t );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data.sectionsPending.push_back( SectionItem {
|
||||
.start = -1,
|
||||
.end = t,
|
||||
.id = ev.id
|
||||
} );
|
||||
m_data.sectionsActive.insert( ev.id );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Worker::MemAllocChanged( MemData& memdata, int64_t time )
|
||||
{
|
||||
const auto val = (double)memdata.usage;
|
||||
|
||||
@@ -407,10 +407,6 @@ private:
|
||||
bool hasBranchRetirement = false;
|
||||
|
||||
unordered_flat_map<uint64_t, uint64_t> fiberToThreadMap;
|
||||
|
||||
Vector<SectionItem> sections;
|
||||
Vector<SectionItem> sectionsPending;
|
||||
unordered_flat_set<uint64_t> sectionsActive;
|
||||
};
|
||||
|
||||
struct MbpsBlock
|
||||
@@ -850,8 +846,6 @@ private:
|
||||
tracy_force_inline void ProcessThreadGroupHint( const QueueThreadGroupHint& ev );
|
||||
tracy_force_inline void ProcessFiberEnter( const QueueFiberEnter& ev );
|
||||
tracy_force_inline void ProcessFiberLeave( const QueueFiberLeave& ev );
|
||||
tracy_force_inline void ProcessSectionEnter( const QueueSectionEnter& ev );
|
||||
tracy_force_inline void ProcessSectionLeave( const QueueSectionLeave& ev );
|
||||
|
||||
tracy_force_inline ZoneEvent* AllocZoneEvent();
|
||||
tracy_force_inline void ProcessZoneBeginImpl( ZoneEvent* zone, const QueueZoneBegin& ev );
|
||||
|
||||
26
stracy/CMakeLists.txt
Normal file
26
stracy/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
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)
|
||||
460
stracy/src/stracy.cpp
Normal file
460
stracy/src/stracy.cpp
Normal file
@@ -0,0 +1,460 @@
|
||||
#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