From 056faddb24d499f2a6d5d458ae5693ec9bdb8978 Mon Sep 17 00:00:00 2001 From: Lectem Date: Thu, 29 Oct 2026 13:06:08 +0100 Subject: [PATCH] Store profiler history in a single history.json Replace the binary connection.history file and the ConnectionHistory class with History, which keeps the history in a JSON file and writes it back when the profiler exits. Existing history is not migrated. --- profiler/CMakeLists.txt | 2 +- profiler/src/ConnectionHistory.cpp | 87 ------------------------- profiler/src/ConnectionHistory.hpp | 32 --------- profiler/src/History.cpp | 101 +++++++++++++++++++++++++++++ profiler/src/History.hpp | 35 ++++++++++ profiler/src/main.cpp | 18 ++--- 6 files changed, 146 insertions(+), 129 deletions(-) delete mode 100644 profiler/src/ConnectionHistory.cpp delete mode 100644 profiler/src/ConnectionHistory.hpp create mode 100644 profiler/src/History.cpp create mode 100644 profiler/src/History.hpp diff --git a/profiler/CMakeLists.txt b/profiler/CMakeLists.txt index 0ef54e03..f9118342 100644 --- a/profiler/CMakeLists.txt +++ b/profiler/CMakeLists.txt @@ -157,9 +157,9 @@ endif() list(TRANSFORM SERVER_FILES PREPEND "src/profiler/") set(PROFILER_FILES - src/ConnectionHistory.cpp src/Filters.cpp src/Fonts.cpp + src/History.cpp src/HttpRequest.cpp src/ImGuiContext.cpp src/ini.c diff --git a/profiler/src/ConnectionHistory.cpp b/profiler/src/ConnectionHistory.cpp deleted file mode 100644 index eddad3fb..00000000 --- a/profiler/src/ConnectionHistory.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include -#include - -#include "ConnectionHistory.hpp" - -#include "../../server/tracy_pdqsort.h" -#include "profiler/TracyStorage.hpp" - - -ConnectionHistory::ConnectionHistory() - : m_fn( tracy::GetSavePath( "connection.history" ) ) -{ - FILE* f = fopen( m_fn.c_str(), "rb" ); - if( !f ) return; - - uint64_t sz; - if( fread( &sz, 1, sizeof( sz ), f ) != sizeof( sz ) ) goto err; - for( uint64_t i=0; i= 1024 ) goto err; - char tmp[1024]; - if( fread( tmp, 1, ssz, f ) != ssz ) goto err; - if( fread( &cnt, 1, sizeof( cnt ), f ) != sizeof( cnt ) ) goto err; - m_connHistMap.emplace( std::string( tmp, tmp+ssz ), cnt ); - } - fclose( f ); - - Rebuild(); - return; - -err: - fclose( f ); - m_connHistMap.clear(); -} - -ConnectionHistory::~ConnectionHistory() -{ - FILE* f = fopen( m_fn.c_str(), "wb" ); - if( !f ) return; - - uint64_t sz = uint64_t( m_connHistMap.size() ); - fwrite( &sz, 1, sizeof( uint64_t ), f ); - for( auto& v : m_connHistMap ) - { - sz = uint64_t( v.first.size() ); - fwrite( &sz, 1, sizeof( uint64_t ), f ); - fwrite( v.first.c_str(), 1, sz, f ); - fwrite( &v.second, 1, sizeof( v.second ), f ); - } - fclose( f ); -} - -void ConnectionHistory::Rebuild() -{ - std::vector::const_iterator> vec; - vec.reserve( m_connHistMap.size() ); - for( auto it = m_connHistMap.begin(); it != m_connHistMap.end(); ++it ) - { - vec.emplace_back( it ); - } - tracy::pdqsort_branchless( vec.begin(), vec.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second > rhs->second; } ); - std::swap( m_connHistVec, vec ); -} - -void ConnectionHistory::Count( const std::string& name ) -{ - auto it = m_connHistMap.find( name ); - if( it != m_connHistMap.end() ) - { - it->second++; - } - else - { - m_connHistMap.emplace( name, 1 ); - } - Rebuild(); -} - -void ConnectionHistory::Erase( size_t idx ) -{ - assert( idx < m_connHistVec.size() ); - m_connHistMap.erase( m_connHistVec[idx] ); - Rebuild(); -} diff --git a/profiler/src/ConnectionHistory.hpp b/profiler/src/ConnectionHistory.hpp deleted file mode 100644 index e3c4f605..00000000 --- a/profiler/src/ConnectionHistory.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef __CONNECTIONHISTORY_HPP__ -#define __CONNECTIONHISTORY_HPP__ - -#include -#include -#include -#include - -class ConnectionHistory -{ -public: - ConnectionHistory(); - ~ConnectionHistory(); - - const std::string& Name( size_t idx ) const { return m_connHistVec[idx]->first; } - - void Count( const std::string& name ); - void Erase( size_t idx ); - - bool empty() const { return m_connHistVec.empty(); } - size_t size() const { return m_connHistVec.size(); } - -private: - void Rebuild(); - - std::string m_fn; - - std::unordered_map m_connHistMap; - std::vector::const_iterator> m_connHistVec; -}; - -#endif diff --git a/profiler/src/History.cpp b/profiler/src/History.cpp new file mode 100644 index 00000000..c9632dc9 --- /dev/null +++ b/profiler/src/History.cpp @@ -0,0 +1,101 @@ +#include +#include +#include + +#include + +#include "History.hpp" + +#include "../../server/tracy_pdqsort.h" +#include "profiler/TracyStorage.hpp" + +History::History() + : m_fn( tracy::GetSavePath( "history.json" ) ) +{ + Load(); +} + +History::~History() +{ + Save(); +} + +void History::Load() +{ + FILE* f = fopen( m_fn.c_str(), "rb" ); + if( !f ) return; + + try + { + const auto json = nlohmann::json::parse( f ); + + if( json.contains( "connections" ) && json["connections"].is_array() ) + { + for( auto& v : json["connections"] ) + { + if( !v.is_object() || !v.contains( "address" ) || !v["address"].is_string() ) continue; + auto address = v["address"].get(); + // The welcome window copies the address into a 1024 bytes buffer and treats it as a C string. + if( address.empty() || address.size() >= 1024 || address.find( '\0' ) != std::string::npos ) continue; + const uint64_t count = v.contains( "count" ) && v["count"].is_number_unsigned() ? v["count"].get() : 1; + // A hand-edited file may list the same address twice. + const auto [it, inserted] = m_connectionsMap.try_emplace( std::move( address ), count ); + if( !inserted ) it->second += std::min( count, UINT64_MAX - it->second ); + } + } + } + catch( nlohmann::json::exception& ) + { + } + + fclose( f ); + + Rebuild(); +} + +void History::Save() const +{ + auto connections = nlohmann::json::array(); + for( const auto& c : m_connectionsVec ) connections.push_back( { { "address", c->first }, { "count", c->second } } ); + + const nlohmann::json j = { { "connections", connections } }; + const auto data = j.dump( 2, ' ', false, nlohmann::json::error_handler_t::replace ); + + FILE* f = fopen( m_fn.c_str(), "wb" ); + if( !f ) return; + fwrite( data.c_str(), 1, data.size(), f ); + fclose( f ); +} + +void History::Rebuild() +{ + std::vector::const_iterator> vec; + vec.reserve( m_connectionsMap.size() ); + for( auto it = m_connectionsMap.begin(); it != m_connectionsMap.end(); ++it ) + { + vec.emplace_back( it ); + } + tracy::pdqsort_branchless( vec.begin(), vec.end(), []( const auto& lhs, const auto& rhs ) { return lhs->second > rhs->second; } ); + std::swap( m_connectionsVec, vec ); +} + +void History::CountConnection( const std::string& address ) +{ + auto it = m_connectionsMap.find( address ); + if( it != m_connectionsMap.end() ) + { + it->second++; + } + else + { + m_connectionsMap.emplace( address, 1 ); + } + Rebuild(); +} + +void History::EraseConnection( size_t idx ) +{ + assert( idx < m_connectionsVec.size() ); + m_connectionsMap.erase( m_connectionsVec[idx] ); + Rebuild(); +} diff --git a/profiler/src/History.hpp b/profiler/src/History.hpp new file mode 100644 index 00000000..92a1dce3 --- /dev/null +++ b/profiler/src/History.hpp @@ -0,0 +1,35 @@ +#ifndef __HISTORY_HPP__ +#define __HISTORY_HPP__ + +#include +#include +#include +#include +#include + +// Persisted connection history, stored in history.json in the profiler's save +// directory. +class History +{ +public: + History(); + ~History(); + + // Client addresses, most used first. + size_t ConnectionCount() const { return m_connectionsVec.size(); } + const std::string& ConnectionName( size_t idx ) const { return m_connectionsVec[idx]->first; } + void CountConnection( const std::string& address ); + void EraseConnection( size_t idx ); + +private: + void Load(); + void Save() const; + void Rebuild(); + + std::string m_fn; + + std::unordered_map m_connectionsMap; + std::vector::const_iterator> m_connectionsVec; +}; + +#endif diff --git a/profiler/src/main.cpp b/profiler/src/main.cpp index 98e5aeac..bc6e0e08 100644 --- a/profiler/src/main.cpp +++ b/profiler/src/main.cpp @@ -65,10 +65,10 @@ #include "zigzag32.hpp" #include "Backend.hpp" -#include "ConnectionHistory.hpp" #include "EmscriptenShim.hpp" #include "Filters.hpp" #include "Fonts.hpp" +#include "History.hpp" #include "HttpRequest.hpp" #include "IsElevated.hpp" #include "ImGuiContext.hpp" @@ -105,7 +105,7 @@ static std::mutex resolvLock; static tracy::unordered_flat_map resolvMap; static ResolvService resolv( port ); static char addr[1024] = { "127.0.0.1" }; -static ConnectionHistory* connHist; +static History* hist; static std::atomic viewShutdown { ViewShutdown::False }; static double animTime = 0; static float dpiScale = -1.f; @@ -332,11 +332,11 @@ int main( int argc, char** argv ) } } - ConnectionHistory connHistory; + History history; Filters filters; tracy::AchievementsMgr achievements; - connHist = &connHistory; + hist = &history; filt = &filters; s_achievements = &achievements; @@ -973,16 +973,16 @@ static void DrawContents() ImGui::TextUnformatted( "Client address" ); bool connectClicked = false; connectClicked |= ImGui::InputTextWithHint( "###connectaddress", "Enter address", addr, 1024, ImGuiInputTextFlags_EnterReturnsTrue ); - if( !connHist->empty() ) + if( hist->ConnectionCount() != 0 ) { ImGui::SameLine(); if( ImGui::BeginCombo( "##frameCombo", nullptr, ImGuiComboFlags_NoPreview ) ) { int idxRemove = -1; - const auto sz = std::min( 5, connHist->size() ); + const auto sz = std::min( 5, hist->ConnectionCount() ); for( size_t i=0; iName( i ); + const auto& str = hist->ConnectionName( i ); if( ImGui::Selectable( str.c_str() ) ) { memcpy( addr, str.c_str(), str.size() + 1 ); @@ -994,7 +994,7 @@ static void DrawContents() } if( idxRemove >= 0 ) { - connHist->Erase( idxRemove ); + hist->EraseConnection( idxRemove ); } ImGui::EndCombo(); } @@ -1017,7 +1017,7 @@ static void DrawContents() if( aptr != aend ) { std::string address( aptr, aend ); - connHist->Count( address ); + hist->CountConnection( address ); auto adata = address.data(); auto ptr = adata + address.size() - 1;