Collect entry stacks for "was this symbol reached" statistics.

SymbolStats now has two additional entry stack maps, which record the
call stacks below every occurrence of a symbol in a sample, not only
when the symbol was at the top of the stack:

- wasReached counts each occurrence separately. If a symbol re-enters
  itself through recursion, every re-entry adds an entry stack, which
  will itself contain the symbol somewhere below.
- wasReachedNonReentrant counts only the outermost occurrence, so each
  sample contributes exactly once and recursion is ignored. The sum of
  counts in this map equals the symbol's inclusive sample count.

To achieve this, the call stack is walked bottom-up, which makes the
first encountered occurrence of a symbol the outermost one. Symbols at
inline positions get the remaining inline frames of their frame group
as a synthetic frame, mirroring what was already done for the top of
the stack.

The wasExecuting and wasExecutingBase maps are now filled by the same
walk, as its topmost-frame special case. The keys they receive are
identical to what the previous code produced.

Note that there is no "base" variant of the reached maps. A base symbol
is present as the last frame of every frame group that contains its
inline functions, so its wasReached map already covers the whole symbol
at base granularity. Merging in the inline symbols' maps, as done for
wasExecutingBase, would only multi-count the same samples.
This commit is contained in:
Bartosz Taudul
2026-07-25 17:02:42 +02:00
parent 2e23c112bd
commit 1dfcacd802
2 changed files with 43 additions and 17 deletions
+2
View File
@@ -879,6 +879,8 @@ struct SymbolStats
uint32_t incl, excl;
unordered_flat_map<uint32_t, uint32_t> wasExecuting;
unordered_flat_map<uint32_t, uint32_t> wasExecutingBase;
unordered_flat_map<uint32_t, uint32_t> wasReached;
unordered_flat_map<uint32_t, uint32_t> wasReachedNonReentrant;
};
+41 -17
View File
@@ -7853,25 +7853,49 @@ void Worker::UpdateSampleStatisticsImpl( const CallstackFrameData** frames, uint
}
};
const bool topFrameHasInlines = fxsz != 1;
CallstackFrameId parentFrameId;
if( topFrameHasInlines )
// Walk the call stack bottom-up, so that the first occurrence of a symbol is the
// outermost one. Entry stacks are accumulated for each symbol on the way. Frames
// within a group are inline expansions, ordered innermost-first, hence the reverse
// iteration order.
seen.clear();
auto suffixIdx = InternSyntheticCallstack( nullptr, cs, framesCount );
for( int32_t c=framesCount-1; c>=0; c-- )
{
const CallstackFrameData cfd = {
.data = fexcl->data + 1,
.size = uint8_t( fxsz-1 ),
.imageName = fexcl->imageName
};
parentFrameId = InternSyntheticFrame( cfd );
const auto fdat = frames[c];
const auto fsz = fdat->size;
for( int32_t f=fsz-1; f>=0; f-- )
{
// suffixIdx contains the call stack below the current frame group. Symbols at
// inline positions also get the remaining inline frames of their own group, as
// a synthetic frame.
const auto symAddr = fdat->data[f].symAddr;
uint32_t idx;
if( f == fsz-1 )
{
idx = suffixIdx;
}
else
{
const CallstackFrameData cfd = {
.data = fdat->data + f + 1,
.size = uint8_t( fsz - f - 1 ),
.imageName = fdat->imageName
};
const auto synthetic = InternSyntheticFrame( cfd );
idx = InternSyntheticCallstack( &synthetic, cs, c+1 );
}
auto sym = m_data.symbolStats.find( symAddr );
assert( sym != m_data.symbolStats.end() );
UpdateEntry( sym->second.wasReached, idx, count );
if( seen.emplace( symAddr ).second ) UpdateEntry( sym->second.wasReachedNonReentrant, idx, count );
if( c == 0 && f == 0 )
{
UpdateEntry( sym->second.wasExecuting, idx, count );
UpdateEntry( sym->second.wasExecutingBase, suffixIdx, count );
}
}
if( c > 0 ) suffixIdx = InternSyntheticCallstack( nullptr, cs, c );
}
const auto parentIdx = InternSyntheticCallstack( topFrameHasInlines ? &parentFrameId : nullptr, cs, 1 );
const auto baseParentIdx = InternSyntheticCallstack( nullptr, cs, 1 );
sym0 = m_data.symbolStats.find( frame0.symAddr );
UpdateEntry( sym0->second.wasExecuting, parentIdx, count );
UpdateEntry( sym0->second.wasExecutingBase, baseParentIdx, count );
}
uint32_t Worker::InternSyntheticCallstack( const CallstackFrameId* head, const VarArray<CallstackFrameId>& cs, uint16_t from )