Sort child sample vectors lazily.

During live capture, child samples were appended to their per-address
vectors in arrival order. Samples postponed due to missing context
switch data are replayed after newer samples were already processed, so
the vectors could become unordered. All range-limited queries binary
search these vectors by time and would silently return wrong results.
Inserting in sorted order at collection time would require a mid-vector
insertion for every stack frame of every replayed sample, so instead
the vectors are now SortedVector and are sorted lazily when accessed,
following what the inline symbol list already does. This also restores
proper query results for traces with inconsistent sample order.
This commit is contained in:
Bartosz Taudul
2026-07-25 19:31:40 +02:00
parent 3293e263c6
commit c880163f96
5 changed files with 16 additions and 10 deletions

View File

@@ -863,7 +863,7 @@ void GatherIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, b
}
}
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines )
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines )
{
if( !worker.AreSymbolSamplesReady() ) return;
auto sym = worker.GetSymbolData( baseAddr );

View File

@@ -138,7 +138,7 @@ std::string FormatDisassemblyLine( const AsmLine& opcode, Worker& worker, std::v
nlohmann::json JsonDisassembly( uint64_t symAddr, Worker& worker, const View& view );
void GatherIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines );
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, const Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines );
void GatherAdditionalIpStats( uint64_t baseAddr, AddrStatData& as, Worker& worker, bool limitView, const View& view, const char* filename, bool propagateInlines );
}

View File

@@ -685,6 +685,8 @@ struct ChildSample
uint64_t addr;
};
struct ChildSampleSort { bool operator()( const ChildSample& lhs, const ChildSample& rhs ) const { return lhs.time.Val() < rhs.time.Val(); }; };
struct SectionItem
{

View File

@@ -1957,11 +1957,11 @@ Worker::Worker( FileRead& f, EventType::Type eventMask, bool bgTasks, bool allow
auto it = m_data.childSamples.find( addr );
if( it == m_data.childSamples.end() )
{
m_data.childSamples.emplace( addr, Vector<ChildSample>( ChildSample { time, childAddr } ) );
m_data.childSamples.emplace( addr, SortedVector<ChildSample, ChildSampleSort>( ChildSample { time, childAddr } ) );
}
else
{
it->second.push_back_non_empty( ChildSample { time, childAddr } );
it->second.push_back( ChildSample { time, childAddr } );
}
childAddr = addr;
}
@@ -1973,7 +1973,7 @@ Worker::Worker( FileRead& f, EventType::Type eventMask, bool bgTasks, bool allow
}
for( auto& v : m_data.childSamples )
{
pdqsort_branchless( v.second.begin(), v.second.end(), []( const auto& lhs, const auto& rhs ) { return lhs.time.Val() < rhs.time.Val(); } );
v.second.ensure_sorted();
}
std::lock_guard<std::mutex> lock( m_data.lock );
m_data.symbolSamplesReady = true;
@@ -2321,11 +2321,15 @@ const Vector<SampleDataRange>* Worker::GetSamplesForSymbol( uint64_t symAddr ) c
return &it->second;
}
const Vector<ChildSample>* Worker::GetChildSamples( uint64_t addr ) const
const SortedVector<ChildSample, ChildSampleSort>* Worker::GetChildSamples( uint64_t addr )
{
assert( m_data.symbolSamplesReady );
auto it = m_data.childSamples.find( addr );
if( it == m_data.childSamples.end() ) return nullptr;
// Out-of-order insertions may happen during live capture, e.g. when samples postponed
// due to missing context switch data are finally processed. Sorting is done lazily, as
// the vectors are only ever read here.
it->second.ensure_sorted();
return &it->second;
}
#endif
@@ -6674,11 +6678,11 @@ void Worker::ProcessCallstackSampleImplStats( const SampleData& sd, ThreadData&
auto it = m_data.childSamples.find( addr );
if( it == m_data.childSamples.end() )
{
m_data.childSamples.emplace( addr, Vector<ChildSample>( ChildSample { sd.time, childAddr } ) );
m_data.childSamples.emplace( addr, SortedVector<ChildSample, ChildSampleSort>( ChildSample { sd.time, childAddr } ) );
}
else
{
it->second.push_back_non_empty( ChildSample { sd.time, childAddr } );
it->second.push_back( ChildSample { sd.time, childAddr } );
}
childAddr = addr;
}

View File

@@ -342,7 +342,7 @@ private:
unordered_flat_map<uint64_t, unordered_flat_map<CallstackFrameId, uint32_t, CallstackFrameIdHash, CallstackFrameIdCompare>> instructionPointersMap;
unordered_flat_map<uint64_t, Vector<SampleDataRange>> symbolSamples;
unordered_flat_map<CallstackFrameId, Vector<SampleDataRange>, CallstackFrameIdHash, CallstackFrameIdCompare> pendingSymbolSamples;
unordered_flat_map<uint64_t, Vector<ChildSample>> childSamples;
unordered_flat_map<uint64_t, SortedVector<ChildSample, ChildSampleSort>> childSamples;
bool newFramesWereReceived = false;
bool callstackSamplesReady = false;
bool newContextSwitchesReceived = false;
@@ -598,7 +598,7 @@ public:
const VarArray<CallstackFrameId>& GetSyntheticCallstack( uint32_t idx ) const { return *m_data.syntheticCallstackPayload[idx]; }
const CallstackFrameData* GetSyntheticCallstackFrame( const CallstackFrameId& ptr ) const;
const Vector<SampleDataRange>* GetSamplesForSymbol( uint64_t symAddr ) const;
const Vector<ChildSample>* GetChildSamples( uint64_t addr ) const;
const SortedVector<ChildSample, ChildSampleSort>* GetChildSamples( uint64_t addr );
#endif
const CrashEvent& GetCrashEvent() const { return m_data.crashEvent; }