diff --git a/profiler/src/llm/tools.json b/profiler/src/llm/tools.json index 236f930b..3e2d2ee0 100644 --- a/profiler/src/llm/tools.json +++ b/profiler/src/llm/tools.json @@ -200,5 +200,26 @@ "required": ["address"] } } +}, +{ + "type": "function", + "function": { + "name": "symbol_parents", + "description": "Get the top N entry call stacks for a given symbol address. Shows the call paths through which execution reached this function.", + "parameters": { + "type": "object", + "properties": { + "address": { + "type": "string", + "description": "Symbol address, for example `0x12345678`." + }, + "limit": { + "type": "integer", + "description": "Maximum number of call stacks to return. Default: 10." + } + }, + "required": ["address"] + } + } } ] diff --git a/profiler/src/profiler/TracyLlmChat.cpp b/profiler/src/profiler/TracyLlmChat.cpp index 9bb6ac6a..0ed74bb6 100644 --- a/profiler/src/profiler/TracyLlmChat.cpp +++ b/profiler/src/profiler/TracyLlmChat.cpp @@ -147,6 +147,18 @@ std::string TracyLlmChat::ToolCallDescription( const nlohmann::json& json ) cons if( sym->isInline ) return ""; return "Disassemble symbol: " + std::string( m_worker.GetString( sym->name ) ); } + else if( name == "symbol_parents" ) + { + if( !args.contains( "address" ) ) return ""; + auto addr = args["address"].get_ref(); + auto symAddr = strtoull( addr.c_str(), nullptr, 16 ); + auto sym = m_worker.GetSymbolData( symAddr ); + if( !sym ) return ""; + if( sym->isInline ) return ""; + std::string limit; + if( args.contains( "limit" ) ) limit = ", limit: " + std::to_string( args["limit"].get() ); + return "Symbol parents: " + std::string( m_worker.GetString( sym->name ) ) + limit; + } return ""; } diff --git a/profiler/src/profiler/TracyLlmTools.cpp b/profiler/src/profiler/TracyLlmTools.cpp index f3fdd16d..587ca54a 100644 --- a/profiler/src/profiler/TracyLlmTools.cpp +++ b/profiler/src/profiler/TracyLlmTools.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "TracyConfig.hpp" #include "TracyDisassembly.hpp" @@ -15,9 +16,12 @@ #include "TracyLlmApi.hpp" #include "TracyLlmTools.hpp" #include "TracyManualData.hpp" +#include "TracyPrint.hpp" #include "TracyStorage.hpp" #include "TracyUtility.hpp" +#include "TracyView.hpp" #include "TracyWorker.hpp" +#include "tracy_pdqsort.h" constexpr const char* NoNetworkAccess = "Internet access is disabled by the user. Inform the user that they may enable it in the settings, so that you can use the tools to gather information."; @@ -194,6 +198,12 @@ std::string TracyLlmTools::HandleToolCalls( const std::string& tool, const nlohm { return SymbolDisasm( Param( "address" ) ); } +#ifndef TRACY_NO_STATISTICS + else if( tool == "symbol_parents" ) + { + return SymbolParents( Param( "address" ), ParamOptU32( "limit", 10 ) ); + } +#endif return "Unknown tool call: " + tool; } catch( const std::exception& e ) @@ -1028,4 +1038,74 @@ std::string TracyLlmTools::SymbolDisasm( const std::string& address ) const return json.dump( -1, ' ', false, nlohmann::json::error_handler_t::replace ); } +#ifndef TRACY_NO_STATISTICS +std::string TracyLlmTools::SymbolParents( const std::string& address, uint32_t limit ) const +{ + uint64_t symAddr = strtoull( address.c_str(), nullptr, 16 ); + auto ss = m_worker.GetSymbolStats( symAddr ); + if( !ss ) return "No parent callstack data for this symbol."; + + const auto symbol = m_worker.GetSymbolData( symAddr ); + if( !symbol ) return "Symbol not found."; + if( symbol->isInline ) return "Symbol is inline."; + + auto stats = ss->parents; + auto excl = ss->excl; + + const auto symLen = symbol->size.Val(); + auto inSym = m_worker.GetInlineSymbolList( symAddr, symLen ); + if( inSym ) + { + const auto symEnd = symAddr + symLen; + while( *inSym < symEnd ) + { + auto istat = m_worker.GetSymbolStats( *inSym++ ); + if( !istat ) continue; + excl += istat->excl; + for( auto& v : istat->baseParents ) + { + auto it = stats.find( v.first ); + if( it == stats.end() ) + { + stats[v.first] = v.second; + } + else + { + it->second += v.second; + } + } + } + } + if( stats.empty() ) return "No parent callstack data for this symbol."; + + std::vector sorted; + sorted.reserve( stats.size() ); + for( auto it = stats.begin(); it != stats.end(); ++it ) sorted.push_back( it ); + pdqsort_branchless( sorted.begin(), sorted.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second > rhs->second; } ); + if( sorted.size() > limit ) sorted.resize( limit ); + + nlohmann::json result = { + { "entries", nlohmann::json::array() }, + { "hint", "Frame N is where frame N-1 returns to. The caller of frame N-1 may differ from frame N." } + }; + auto& entries = result["entries"]; + + for( auto& entry : sorted ) + { + auto& cs = m_worker.GetParentCallstack( entry->first ); + auto frames = m_view.GetCallstackJson( cs.data(), cs.size() )["frames"]; + + char buf[32]; + auto end = PrintFloat( buf, buf+32, 100.f * entry->second / excl, 4 ); + *end = '\0'; + + entries.push_back( { + { "callstack", frames }, + { "percentage", buf } + } ); + } + return result.dump( -1, ' ', false, nlohmann::json::error_handler_t::replace ); +} +#endif + } diff --git a/profiler/src/profiler/TracyLlmTools.hpp b/profiler/src/profiler/TracyLlmTools.hpp index 34134408..673c48b1 100644 --- a/profiler/src/profiler/TracyLlmTools.hpp +++ b/profiler/src/profiler/TracyLlmTools.hpp @@ -66,6 +66,9 @@ private: std::string SourceSearch( std::string query, bool caseInsensitive, const std::string& path ) const; std::string GetSkill( const std::string& name ) const; std::string SymbolDisasm( const std::string& address ) const; +#ifndef TRACY_NO_STATISTICS + std::string SymbolParents( const std::string& address, uint32_t limit ) const; +#endif void ManualEmbeddingsWorker( TracyLlmApi& api );