Bug 1577658 - Factor DoStreamSamplesToJSON out of ProfileBuffer::StreamSamplesToJSON - r=canaltinova

This refactoring is a bit more complex (to prepare for the next patch).

The body of StreamSamplesToJSON was moved to a function that takes a lambda, which will provide streaming parameters relevant to the thread id of each read sample.
For now, it's only used by StreamSamplesToJSON, so in this case the lambda only checks if the entry thread id is the expected one, and it provides the JSON writer and other things needed to write these samples.

One extra complexity is that the `ProfileSample sample` that was outside the loop is now removed -- because in later patches this code will handle samples from different threads, so they cannot all share this one sample object. Instead the information that must be carried over is in the streaming parameters (mPreviousStackState and mPreviousStack), so that they correspond to one thread each. But the overall logic is the same, apart from where this out-of-the-loop information lives.

And another difference is that old samples (before `aSinceTime`) are fully processed, and then discarded just before being output, because we need their information to be in the thread's UniqueStacks, in case it's used by a later same-as-before sample.

Differential Revision: https://phabricator.services.mozilla.com/D128443
This commit is contained in:
Gerald Squelart 2021-10-21 05:47:25 +00:00
parent 8bca70e031
commit 1538b47534
2 changed files with 119 additions and 54 deletions

View File

@ -176,6 +176,14 @@ class ProfileBuffer final {
mozilla::ProfileBufferChunk::SizeofChunkMetadata() +
mozilla::ProfileBufferChunkManager::scExpectedMaximumStackSize)};
// GetStreamingParametersForThreadCallback:
// (ProfilerThreadId) -> Maybe<StreamingParametersForThread>
template <typename GetStreamingParametersForThreadCallback>
ProfilerThreadId DoStreamSamplesToJSON(
GetStreamingParametersForThreadCallback&&
aGetStreamingParametersForThreadCallback,
double aSinceTime) const;
double mFirstSamplingTimeUs = 0.0;
double mLastSamplingTimeUs = 0.0;
ProfilerStats mIntervalsUs;

View File

@ -9,6 +9,7 @@
#include "mozilla/ProfilerMarkers.h"
#include "platform.h"
#include "ProfileBuffer.h"
#include "ProfiledThreadData.h"
#include "ProfilerBacktrace.h"
#include "ProfilerRustBindings.h"
@ -542,7 +543,6 @@ void JITFrameInfo::AddInfoForRange(
struct ProfileSample {
uint32_t mStack = 0;
bool mStackIsEmpty = false;
double mTime = 0.0;
Maybe<double> mResponsiveness;
RunningTimes mRunningTimes;
@ -823,9 +823,29 @@ class EntryGetter {
continue; \
}
ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
SpliceableJSONWriter& aWriter, ProfilerThreadId aThreadId,
double aSinceTime, UniqueStacks& aUniqueStacks) const {
struct StreamingParametersForThread {
SpliceableJSONWriter& mWriter;
UniqueStacks& mUniqueStacks;
ThreadStreamingContext::PreviousStackState& mPreviousStackState;
uint32_t& mPreviousStack;
StreamingParametersForThread(
SpliceableJSONWriter& aWriter, UniqueStacks& aUniqueStacks,
ThreadStreamingContext::PreviousStackState& aPreviousStackState,
uint32_t& aPreviousStack)
: mWriter(aWriter),
mUniqueStacks(aUniqueStacks),
mPreviousStackState(aPreviousStackState),
mPreviousStack(aPreviousStack) {}
};
// GetStreamingParametersForThreadCallback:
// (ProfilerThreadId) -> Maybe<StreamingParametersForThread>
template <typename GetStreamingParametersForThreadCallback>
ProfilerThreadId ProfileBuffer::DoStreamSamplesToJSON(
GetStreamingParametersForThreadCallback&&
aGetStreamingParametersForThreadCallback,
double aSinceTime) const {
UniquePtr<char[]> dynStrBuf = MakeUnique<char[]>(kMaxFrameKeyLength);
return mEntries.Read([&](ProfileChunkedBuffer::Reader* aReader) {
@ -835,11 +855,6 @@ ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
ProfilerThreadId processedThreadId;
// This ProfileSample object gets filled with relevant data related to each
// sample. Parts of it may be reused from one sample to the next, in
// particular when stacks are identical in `SameSample` entries.
ProfileSample sample;
EntryGetter e(*aReader);
for (;;) {
@ -872,20 +887,26 @@ ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
ProfilerThreadId threadId = e.Get().GetThreadId();
e.Next();
Maybe<StreamingParametersForThread> streamingParameters =
std::forward<GetStreamingParametersForThreadCallback>(
aGetStreamingParametersForThreadCallback)(threadId);
// Ignore samples that are for the wrong thread.
if (threadId != aThreadId && aThreadId.IsSpecified()) {
if (!streamingParameters) {
continue;
}
MOZ_ASSERT(
aThreadId.IsSpecified() || !processedThreadId.IsSpecified(),
"Unspecified aThreadId should only be used with 1-sample buffer");
SpliceableJSONWriter& writer = streamingParameters->mWriter;
UniqueStacks& uniqueStacks = streamingParameters->mUniqueStacks;
ThreadStreamingContext::PreviousStackState& previousStackState =
streamingParameters->mPreviousStackState;
uint32_t& previousStack = streamingParameters->mPreviousStack;
auto ReadStack = [&](EntryGetter& e, uint64_t entryPosition,
auto ReadStack = [&](EntryGetter& e, double time, uint64_t entryPosition,
const Maybe<double>& unresponsiveDuration,
const RunningTimes& aRunningTimes) {
const RunningTimes& runningTimes) {
UniqueStacks::StackKey stack =
aUniqueStacks.BeginStack(UniqueStacks::FrameKey("(root)"));
uniqueStacks.BeginStack(UniqueStacks::FrameKey("(root)"));
int numFrames = 0;
while (e.Has()) {
@ -897,8 +918,8 @@ ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
nsAutoCString buf;
if (!aUniqueStacks.mCodeAddressService ||
!aUniqueStacks.mCodeAddressService->GetFunction(pc, buf) ||
if (!uniqueStacks.mCodeAddressService ||
!uniqueStacks.mCodeAddressService->GetFunction(pc, buf) ||
buf.IsEmpty()) {
buf.AppendASCII("0x");
// `AppendInt` only knows `uint32_t` or `uint64_t`, but because
@ -915,8 +936,8 @@ ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
16);
}
stack = aUniqueStacks.AppendFrame(
stack, UniqueStacks::FrameKey(buf.get()));
stack = uniqueStacks.AppendFrame(stack,
UniqueStacks::FrameKey(buf.get()));
} else if (e.Get().IsLabel()) {
numFrames++;
@ -1003,7 +1024,7 @@ ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
e.Next();
}
stack = aUniqueStacks.AppendFrame(
stack = uniqueStacks.AppendFrame(
stack,
UniqueStacks::FrameKey(std::move(frameLabel), relevantForJS,
isBaselineInterp, innerWindowID, line,
@ -1015,14 +1036,14 @@ ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
// A JIT frame may expand to multiple frames due to inlining.
void* pc = e.Get().GetPtr();
const Maybe<Vector<UniqueStacks::FrameKey>>& frameKeys =
aUniqueStacks.LookupFramesForJITAddressFromBufferPos(
uniqueStacks.LookupFramesForJITAddressFromBufferPos(
pc, entryPosition ? entryPosition : e.CurPos());
MOZ_RELEASE_ASSERT(
frameKeys,
"Attempting to stream samples for a buffer range "
"for which we don't have JITFrameInfo?");
for (const UniqueStacks::FrameKey& frameKey : *frameKeys) {
stack = aUniqueStacks.AppendFrame(stack, frameKey);
stack = uniqueStacks.AppendFrame(stack, frameKey);
}
e.Next();
@ -1035,45 +1056,49 @@ ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
// Even if this stack is considered empty, it contains the root frame,
// which needs to be in the JSON output because following "same samples"
// may refer to it when reusing this sample.mStack.
sample.mStack = aUniqueStacks.GetOrAddStackIndex(stack);
sample.mStackIsEmpty = (numFrames == 0);
const uint32_t stackIndex = uniqueStacks.GetOrAddStackIndex(stack);
if (sample.mStackIsEmpty && aRunningTimes.IsEmpty()) {
// And store that possibly-empty stack in case it's followed by "same
// sample" entries.
previousStack = stackIndex;
previousStackState = (numFrames == 0)
? ThreadStreamingContext::eStackWasEmpty
: ThreadStreamingContext::eStackWasNotEmpty;
// Even if too old or empty, we did process a sample for this thread id.
processedThreadId = threadId;
// Discard samples that are too old.
if (time < aSinceTime) {
return;
}
if (numFrames == 0 && runningTimes.IsEmpty()) {
// It is possible to have empty stacks if native stackwalking is
// disabled. Skip samples with empty stacks, unless we have useful
// running times.
return;
}
if (unresponsiveDuration.isSome()) {
sample.mResponsiveness = unresponsiveDuration;
}
sample.mRunningTimes = aRunningTimes;
WriteSample(aWriter, sample);
processedThreadId = threadId;
WriteSample(writer, ProfileSample{stackIndex, time,
unresponsiveDuration, runningTimes});
}; // End of `ReadStack(EntryGetter&)` lambda.
if (e.Has() && e.Get().IsTime()) {
sample.mTime = e.Get().GetDouble();
double time = e.Get().GetDouble();
e.Next();
// Note: Even if this sample is too old (before aSinceTime), we still
// need to read it, so that its frames are in the tables, in case there
// is a same-sample following it that would be after aSinceTime, which
// would need these frames to be present.
// Ignore samples that are too old.
if (sample.mTime < aSinceTime) {
continue;
}
ReadStack(e, 0, Nothing{}, RunningTimes{});
ReadStack(e, time, 0, Nothing{}, RunningTimes{});
} else if (e.Has() && e.Get().IsTimeBeforeCompactStack()) {
sample.mTime = e.Get().GetDouble();
// Ignore samples that are too old.
if (sample.mTime < aSinceTime) {
e.Next();
continue;
}
double time = e.Get().GetDouble();
// Note: Even if this sample is too old (before aSinceTime), we still
// need to read it, so that its frames are in the tables, in case there
// is a same-sample following it that would be after aSinceTime, which
// would need these frames to be present.
RunningTimes runningTimes;
Maybe<double> unresponsiveDuration;
@ -1108,8 +1133,9 @@ ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
tempBuffer.Read([&](ProfileChunkedBuffer::Reader* aReader) {
MOZ_ASSERT(aReader,
"Local ProfileChunkedBuffer cannot be out-of-session");
// This is a compact stack, it should only contain one sample.
EntryGetter stackEntryGetter(*aReader);
ReadStack(stackEntryGetter,
ReadStack(stackEntryGetter, time,
it.CurrentBlockIndex().ConvertToProfileBufferIndex(),
unresponsiveDuration, runningTimes);
});
@ -1125,16 +1151,18 @@ ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
e.RestartAfter(it);
} else if (e.Has() && e.Get().IsTimeBeforeSameSample()) {
if (sample.mTime == 0.0) {
if (previousStackState == ThreadStreamingContext::eNoStackYet) {
// We don't have any full sample yet, we cannot duplicate a "previous"
// one. This should only happen at most once per thread, for the very
// first sample.
continue;
}
ProfileSample sample;
// Keep the same `mStack` as previously output.
// Note that it may be empty, this is checked below before writing it.
(void)sample.mStack;
sample.mStack = previousStack;
sample.mTime = e.Get().GetDouble();
@ -1165,12 +1193,13 @@ ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
}
if (kind == ProfileBufferEntry::Kind::SameSample) {
if (sample.mStackIsEmpty && sample.mRunningTimes.IsEmpty()) {
if (previousStackState == ThreadStreamingContext::eStackWasEmpty &&
sample.mRunningTimes.IsEmpty()) {
// Skip samples with empty stacks, unless we have useful running
// times.
break;
}
WriteSample(aWriter, sample);
WriteSample(writer, sample);
break;
}
@ -1190,6 +1219,34 @@ ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
});
}
ProfilerThreadId ProfileBuffer::StreamSamplesToJSON(
SpliceableJSONWriter& aWriter, ProfilerThreadId aThreadId,
double aSinceTime, UniqueStacks& aUniqueStacks) const {
ThreadStreamingContext::PreviousStackState previousStackState =
ThreadStreamingContext::eNoStackYet;
uint32_t stack = 0u;
#ifdef DEBUG
int processedCount = 0;
#endif // DEBUG
return DoStreamSamplesToJSON(
[&](ProfilerThreadId aReadThreadId) {
Maybe<StreamingParametersForThread> streamingParameters;
#ifdef DEBUG
++processedCount;
MOZ_ASSERT(
aThreadId.IsSpecified() ||
(processedCount == 1 && aReadThreadId.IsSpecified()),
"Unspecified aThreadId should only be used with 1-sample buffer");
#endif // DEBUG
if (!aThreadId.IsSpecified() || aThreadId == aReadThreadId) {
streamingParameters.emplace(aWriter, aUniqueStacks,
previousStackState, stack);
}
return streamingParameters;
},
aSinceTime);
}
void ProfileBuffer::AddJITInfoForRange(uint64_t aRangeStart,
ProfilerThreadId aThreadId,
JSContext* aContext,