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.
This commit is contained in:
Lectem
2026-10-29 20:36:22 +01:00
parent 9f1c8e652c
commit 056faddb24
6 changed files with 146 additions and 129 deletions
+1 -1
View File
@@ -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
-87
View File
@@ -1,87 +0,0 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#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<sz; i++ )
{
uint64_t ssz, cnt;
if( fread( &ssz, 1, sizeof( ssz ), f ) != sizeof( ssz ) ) goto err;
if( ssz >= 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<std::unordered_map<std::string, uint64_t>::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();
}
-32
View File
@@ -1,32 +0,0 @@
#ifndef __CONNECTIONHISTORY_HPP__
#define __CONNECTIONHISTORY_HPP__
#include <stdint.h>
#include <string>
#include <unordered_map>
#include <vector>
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<std::string, uint64_t> m_connHistMap;
std::vector<std::unordered_map<std::string, uint64_t>::const_iterator> m_connHistVec;
};
#endif
+101
View File
@@ -0,0 +1,101 @@
#include <algorithm>
#include <assert.h>
#include <stdio.h>
#include <nlohmann/json.hpp>
#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<std::string>();
// 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<uint64_t>() : 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<std::unordered_map<std::string, uint64_t>::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();
}
+35
View File
@@ -0,0 +1,35 @@
#ifndef __HISTORY_HPP__
#define __HISTORY_HPP__
#include <stddef.h>
#include <stdint.h>
#include <string>
#include <unordered_map>
#include <vector>
// 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<std::string, uint64_t> m_connectionsMap;
std::vector<std::unordered_map<std::string, uint64_t>::const_iterator> m_connectionsVec;
};
#endif
+9 -9
View File
@@ -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<std::string, std::string> resolvMap;
static ResolvService resolv( port );
static char addr[1024] = { "127.0.0.1" };
static ConnectionHistory* connHist;
static History* hist;
static std::atomic<ViewShutdown> 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<size_t>( 5, connHist->size() );
const auto sz = std::min<size_t>( 5, hist->ConnectionCount() );
for( size_t i=0; i<sz; i++ )
{
const auto& str = connHist->Name( 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;