Context switch samples were appended in arrival order. Samples which
were postponed due to missing context switch data are replayed after
newer samples were already classified, so the vector could become
unordered. Everything that reads it assumes time order: the wait stacks
range filter, the sampling statistics percentage denominator, and the
context switch sample filters in the trace load jobs. In the load jobs
an unordered vector could silently disable the filtering for the rest
of a thread, reintroducing the context switch samples into the symbol
and child sample maps.
Use a SortedVector and restore the ordering at the points where it can
break: after the postponed sample replay and when saving a trace. The
save file version is bumped, so that the sort order check on load is
only performed for traces saved by previous versions.
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.
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.
The current "parents" mode of operation is "was this symbol executing".
The new name for the maps reflects that.
There should also be "was this symbol reached" (both recursive and non-
reentrant) modes.
Many of the zones would have a negative running time due to a missing `cs->IsEndValid()` check.
This could end reporting context switches before the zone start, due to `cs->End()` returning -1.
This happened when systrace dropped event, or when using Fibers and `TracyFiberEnter` is called on the new thread once the fiber has been scheduled. (The manual actually does not really hint this is wrong, we should probably fix the manual or the server code.)
In both cases, we assume runtime to be 0 for that context switch. Since we have no actual information. Both options (counting full runtime or no runtime) are wrong, and most of the code handling `!cs->IsEndValid()` uses `Start` instead so that's what I did. This is still a net improvement over displaying negative values. If we want to change this handling, we'd need to review the other places that do `it->IsEndValid() ? it->End() : it->Start()` as well.
It also seems two different concepts were being mixed:
1. Do we have any context switch data at all ? (`it != ctx->v.end()` ie `count != 0`)
2. Do we have complete data for the last context switch (`eit != ctx->v.end()`)
This led to some places of the code not displaying or counting running time at all, notably when hovering a zone.
I think most of the time we wanted 1, as it reports correctly and assumes the last context switch is still running, which is a fair assumption if we didn't see one putting the thread to sleep.
I also fixed a case where we were overcounting runtime when range start was during a sleep.
This PR does the following things:
- Allow to lock the highlighted thread using left mouse click or lane popup menu
- Add more descriptions for wait reasons in View::DecodeContextSwitchReason
- Stores the Wakeup CPU so that we may display it in the CPU data view
- `QueueThreadWakeup` now contains cpu and adjust info (currently unused). Fits in the padding, no struct size change.
- `QueueContextSwitch` now also contains thread priorities information (windows+linux) and CState (windows), but currently unused by the profiler. Fits in the padding, no struct size change.
- Fixes Linux `EventWakeup` which tries to remove PERF_SAMPLE_CALLCHAIN on the wrong member. Set the whole `perf_event_attr` explicitely instead.
- Fixes Windows ReadyThread event being dropped if triggered before the thread switches out (this is confirmed to be normal behaviour, probably due to thread being switched out but context switch actually happening later)
- Bumps tracy file version and protocol versions
Context switch callstack samples are not included in the sampling data
statistics (as these don't represent random sampling), but are rather put into
a separate dedicated data structure.
For this to work, a complete context switch data has to be available for the
callstack timestamp. There is no guarantee it will be present at the time it
is needed, so a second structure is added to allow postponing qualification of
callstacks.