diff options
Diffstat (limited to 'tools/profiler/core/ProfiledThreadData.cpp')
-rw-r--r-- | tools/profiler/core/ProfiledThreadData.cpp | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/tools/profiler/core/ProfiledThreadData.cpp b/tools/profiler/core/ProfiledThreadData.cpp new file mode 100644 index 0000000000..515b4a7f72 --- /dev/null +++ b/tools/profiler/core/ProfiledThreadData.cpp @@ -0,0 +1,456 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ProfiledThreadData.h" + +#include "platform.h" +#include "ProfileBuffer.h" + +#include "mozilla/OriginAttributes.h" +#include "mozilla/Span.h" +#include "nsXULAppAPI.h" + +#if defined(GP_OS_darwin) +# include <pthread.h> +#endif + +using namespace mozilla::literals::ProportionValue_literals; + +ProfiledThreadData::ProfiledThreadData( + const mozilla::profiler::ThreadRegistrationInfo& aThreadInfo) + : mThreadInfo(aThreadInfo.Name(), aThreadInfo.ThreadId(), + aThreadInfo.IsMainThread(), aThreadInfo.RegisterTime()) { + MOZ_COUNT_CTOR(ProfiledThreadData); +} + +ProfiledThreadData::ProfiledThreadData( + mozilla::profiler::ThreadRegistrationInfo&& aThreadInfo) + : mThreadInfo(std::move(aThreadInfo)) { + MOZ_COUNT_CTOR(ProfiledThreadData); +} + +ProfiledThreadData::~ProfiledThreadData() { + MOZ_COUNT_DTOR(ProfiledThreadData); +} + +static void StreamTables(UniqueStacks&& aUniqueStacks, JSContext* aCx, + SpliceableJSONWriter& aWriter, + const mozilla::TimeStamp& aProcessStartTime, + mozilla::ProgressLogger aProgressLogger) { + aWriter.StartObjectProperty("stackTable"); + { + { + JSONSchemaWriter schema(aWriter); + schema.WriteField("prefix"); + schema.WriteField("frame"); + } + + aWriter.StartArrayProperty("data"); + { + aProgressLogger.SetLocalProgress(1_pc, "Splicing stack table..."); + aUniqueStacks.SpliceStackTableElements(aWriter); + aProgressLogger.SetLocalProgress(30_pc, "Spliced stack table"); + } + aWriter.EndArray(); + } + aWriter.EndObject(); + + aWriter.StartObjectProperty("frameTable"); + { + { + JSONSchemaWriter schema(aWriter); + schema.WriteField("location"); + schema.WriteField("relevantForJS"); + schema.WriteField("innerWindowID"); + schema.WriteField("implementation"); + schema.WriteField("optimizations"); + schema.WriteField("line"); + schema.WriteField("column"); + schema.WriteField("category"); + schema.WriteField("subcategory"); + } + + aWriter.StartArrayProperty("data"); + { + aProgressLogger.SetLocalProgress(30_pc, "Splicing frame table..."); + aUniqueStacks.SpliceFrameTableElements(aWriter); + aProgressLogger.SetLocalProgress(60_pc, "Spliced frame table"); + } + aWriter.EndArray(); + } + aWriter.EndObject(); + + aWriter.StartArrayProperty("stringTable"); + { + aProgressLogger.SetLocalProgress(60_pc, "Splicing string table..."); + std::move(aUniqueStacks.UniqueStrings()).SpliceStringTableElements(aWriter); + aProgressLogger.SetLocalProgress(90_pc, "Spliced string table"); + } + aWriter.EndArray(); +} + +mozilla::NotNull<mozilla::UniquePtr<UniqueStacks>> +ProfiledThreadData::PrepareUniqueStacks( + const ProfileBuffer& aBuffer, JSContext* aCx, + mozilla::FailureLatch& aFailureLatch, ProfilerCodeAddressService* aService, + mozilla::ProgressLogger aProgressLogger) { + if (mJITFrameInfoForPreviousJSContexts && + mJITFrameInfoForPreviousJSContexts->HasExpired( + aBuffer.BufferRangeStart())) { + mJITFrameInfoForPreviousJSContexts = nullptr; + } + aProgressLogger.SetLocalProgress(1_pc, "Checked JIT frame info presence"); + + // If we have an existing JITFrameInfo in mJITFrameInfoForPreviousJSContexts, + // copy the data from it. + JITFrameInfo jitFrameInfo = + mJITFrameInfoForPreviousJSContexts + ? JITFrameInfo(*mJITFrameInfoForPreviousJSContexts, + aProgressLogger.CreateSubLoggerTo( + "Retrieving JIT frame info...", 10_pc, + "Retrieved JIT frame info")) + : JITFrameInfo(); + + if (aCx && mBufferPositionWhenReceivedJSContext) { + aBuffer.AddJITInfoForRange( + *mBufferPositionWhenReceivedJSContext, mThreadInfo.ThreadId(), aCx, + jitFrameInfo, + aProgressLogger.CreateSubLoggerTo("Adding JIT info...", 90_pc, + "Added JIT info")); + } else { + aProgressLogger.SetLocalProgress(90_pc, "No JIT info"); + } + + return mozilla::MakeNotNull<mozilla::UniquePtr<UniqueStacks>>( + aFailureLatch, std::move(jitFrameInfo), aService); +} + +void ProfiledThreadData::StreamJSON( + const ProfileBuffer& aBuffer, JSContext* aCx, SpliceableJSONWriter& aWriter, + const nsACString& aProcessName, const nsACString& aETLDplus1, + const mozilla::TimeStamp& aProcessStartTime, double aSinceTime, + ProfilerCodeAddressService* aService, + mozilla::ProgressLogger aProgressLogger) { + mozilla::NotNull<mozilla::UniquePtr<UniqueStacks>> uniqueStacks = + PrepareUniqueStacks(aBuffer, aCx, aWriter.SourceFailureLatch(), aService, + aProgressLogger.CreateSubLoggerFromTo( + 0_pc, "Preparing unique stacks...", 10_pc, + "Prepared Unique stacks")); + + aWriter.SetUniqueStrings(uniqueStacks->UniqueStrings()); + + aWriter.Start(); + { + StreamSamplesAndMarkers( + mThreadInfo.Name(), mThreadInfo.ThreadId(), aBuffer, aWriter, + aProcessName, aETLDplus1, aProcessStartTime, mThreadInfo.RegisterTime(), + mUnregisterTime, aSinceTime, *uniqueStacks, + aProgressLogger.CreateSubLoggerTo( + 90_pc, + "ProfiledThreadData::StreamJSON: Streamed samples and markers")); + + StreamTables(std::move(*uniqueStacks), aCx, aWriter, aProcessStartTime, + aProgressLogger.CreateSubLoggerTo( + 99_pc, "Streamed tables and trace logger")); + } + aWriter.End(); + + aWriter.ResetUniqueStrings(); +} + +void ProfiledThreadData::StreamJSON( + ThreadStreamingContext&& aThreadStreamingContext, + SpliceableJSONWriter& aWriter, const nsACString& aProcessName, + const nsACString& aETLDplus1, const mozilla::TimeStamp& aProcessStartTime, + ProfilerCodeAddressService* aService, + mozilla::ProgressLogger aProgressLogger) { + aWriter.Start(); + { + StreamSamplesAndMarkers( + mThreadInfo.Name(), aThreadStreamingContext, aWriter, aProcessName, + aETLDplus1, aProcessStartTime, mThreadInfo.RegisterTime(), + mUnregisterTime, + aProgressLogger.CreateSubLoggerFromTo( + 1_pc, "ProfiledThreadData::StreamJSON(context): Streaming...", + 90_pc, + "ProfiledThreadData::StreamJSON(context): Streamed samples and " + "markers")); + + StreamTables( + std::move(*aThreadStreamingContext.mUniqueStacks), + aThreadStreamingContext.mJSContext, aWriter, aProcessStartTime, + aProgressLogger.CreateSubLoggerTo( + "ProfiledThreadData::StreamJSON(context): Streaming tables...", + 99_pc, "ProfiledThreadData::StreamJSON(context): Streamed tables")); + } + aWriter.End(); +} + +// StreamSamplesDataCallback: (ProgressLogger) -> ProfilerThreadId +// StreamMarkersDataCallback: (ProgressLogger) -> void +// Returns the ProfilerThreadId returned by StreamSamplesDataCallback, which +// should be the thread id of the last sample that was processed (if any; +// otherwise it is left unspecified). This is mostly useful when the caller +// doesn't know where the sample comes from, e.g., when it's a backtrace in a +// marker. +template <typename StreamSamplesDataCallback, + typename StreamMarkersDataCallback> +ProfilerThreadId DoStreamSamplesAndMarkers( + const char* aName, SpliceableJSONWriter& aWriter, + const nsACString& aProcessName, const nsACString& aETLDplus1, + const mozilla::TimeStamp& aProcessStartTime, + const mozilla::TimeStamp& aRegisterTime, + const mozilla::TimeStamp& aUnregisterTime, + mozilla::ProgressLogger aProgressLogger, + StreamSamplesDataCallback&& aStreamSamplesDataCallback, + StreamMarkersDataCallback&& aStreamMarkersDataCallback) { + ProfilerThreadId processedThreadId; + + aWriter.StringProperty("processType", + mozilla::MakeStringSpan(XRE_GetProcessTypeString())); + + aWriter.StringProperty("name", mozilla::MakeStringSpan(aName)); + + // Use given process name (if any), unless we're the parent process. + if (XRE_IsParentProcess()) { + aWriter.StringProperty("processName", "Parent Process"); + } else if (!aProcessName.IsEmpty()) { + aWriter.StringProperty("processName", aProcessName); + } + if (!aETLDplus1.IsEmpty()) { + nsAutoCString originNoSuffix; + mozilla::OriginAttributes attrs; + if (!attrs.PopulateFromOrigin(aETLDplus1, originNoSuffix)) { + aWriter.StringProperty("eTLD+1", aETLDplus1); + } else { + aWriter.StringProperty("eTLD+1", originNoSuffix); + aWriter.BoolProperty("isPrivateBrowsing", attrs.mPrivateBrowsingId > 0); + aWriter.IntProperty("userContextId", attrs.mUserContextId); + } + } + + if (aRegisterTime) { + aWriter.DoubleProperty( + "registerTime", (aRegisterTime - aProcessStartTime).ToMilliseconds()); + } else { + aWriter.NullProperty("registerTime"); + } + + if (aUnregisterTime) { + aWriter.DoubleProperty( + "unregisterTime", + (aUnregisterTime - aProcessStartTime).ToMilliseconds()); + } else { + aWriter.NullProperty("unregisterTime"); + } + + aWriter.StartObjectProperty("samples"); + { + { + JSONSchemaWriter schema(aWriter); + schema.WriteField("stack"); + schema.WriteField("time"); + schema.WriteField("eventDelay"); +#define RUNNING_TIME_FIELD(index, name, unit, jsonProperty) \ + schema.WriteField(#jsonProperty); + PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_FIELD) +#undef RUNNING_TIME_FIELD + } + + aWriter.StartArrayProperty("data"); + { + processedThreadId = std::forward<StreamSamplesDataCallback>( + aStreamSamplesDataCallback)(aProgressLogger.CreateSubLoggerFromTo( + 1_pc, "Streaming samples...", 49_pc, "Streamed samples")); + } + aWriter.EndArray(); + } + aWriter.EndObject(); + + aWriter.StartObjectProperty("markers"); + { + { + JSONSchemaWriter schema(aWriter); + schema.WriteField("name"); + schema.WriteField("startTime"); + schema.WriteField("endTime"); + schema.WriteField("phase"); + schema.WriteField("category"); + schema.WriteField("data"); + } + + aWriter.StartArrayProperty("data"); + { + std::forward<StreamMarkersDataCallback>(aStreamMarkersDataCallback)( + aProgressLogger.CreateSubLoggerFromTo(50_pc, "Streaming markers...", + 99_pc, "Streamed markers")); + } + aWriter.EndArray(); + } + aWriter.EndObject(); + + // Tech note: If `ToNumber()` returns a uint64_t, the conversion to int64_t is + // "implementation-defined" before C++20. This is acceptable here, because + // this is a one-way conversion to a unique identifier that's used to visually + // separate data by thread on the front-end. + aWriter.IntProperty( + "pid", static_cast<int64_t>(profiler_current_process_id().ToNumber())); + aWriter.IntProperty("tid", + static_cast<int64_t>(processedThreadId.ToNumber())); + + return processedThreadId; +} + +ProfilerThreadId StreamSamplesAndMarkers( + const char* aName, ProfilerThreadId aThreadId, const ProfileBuffer& aBuffer, + SpliceableJSONWriter& aWriter, const nsACString& aProcessName, + const nsACString& aETLDplus1, const mozilla::TimeStamp& aProcessStartTime, + const mozilla::TimeStamp& aRegisterTime, + const mozilla::TimeStamp& aUnregisterTime, double aSinceTime, + UniqueStacks& aUniqueStacks, mozilla::ProgressLogger aProgressLogger) { + return DoStreamSamplesAndMarkers( + aName, aWriter, aProcessName, aETLDplus1, aProcessStartTime, + aRegisterTime, aUnregisterTime, std::move(aProgressLogger), + [&](mozilla::ProgressLogger aSubProgressLogger) { + ProfilerThreadId processedThreadId = aBuffer.StreamSamplesToJSON( + aWriter, aThreadId, aSinceTime, aUniqueStacks, + std::move(aSubProgressLogger)); + return aThreadId.IsSpecified() ? aThreadId : processedThreadId; + }, + [&](mozilla::ProgressLogger aSubProgressLogger) { + aBuffer.StreamMarkersToJSON(aWriter, aThreadId, aProcessStartTime, + aSinceTime, aUniqueStacks, + std::move(aSubProgressLogger)); + }); +} + +void StreamSamplesAndMarkers(const char* aName, + ThreadStreamingContext& aThreadData, + SpliceableJSONWriter& aWriter, + const nsACString& aProcessName, + const nsACString& aETLDplus1, + const mozilla::TimeStamp& aProcessStartTime, + const mozilla::TimeStamp& aRegisterTime, + const mozilla::TimeStamp& aUnregisterTime, + mozilla::ProgressLogger aProgressLogger) { + (void)DoStreamSamplesAndMarkers( + aName, aWriter, aProcessName, aETLDplus1, aProcessStartTime, + aRegisterTime, aUnregisterTime, std::move(aProgressLogger), + [&](mozilla::ProgressLogger aSubProgressLogger) { + aWriter.TakeAndSplice( + aThreadData.mSamplesDataWriter.TakeChunkedWriteFunc()); + return aThreadData.mProfiledThreadData.Info().ThreadId(); + }, + [&](mozilla::ProgressLogger aSubProgressLogger) { + aWriter.TakeAndSplice( + aThreadData.mMarkersDataWriter.TakeChunkedWriteFunc()); + }); +} + +void ProfiledThreadData::NotifyAboutToLoseJSContext( + JSContext* aContext, const mozilla::TimeStamp& aProcessStartTime, + ProfileBuffer& aBuffer) { + if (!mBufferPositionWhenReceivedJSContext) { + return; + } + + MOZ_RELEASE_ASSERT(aContext); + + if (mJITFrameInfoForPreviousJSContexts && + mJITFrameInfoForPreviousJSContexts->HasExpired( + aBuffer.BufferRangeStart())) { + mJITFrameInfoForPreviousJSContexts = nullptr; + } + + mozilla::UniquePtr<JITFrameInfo> jitFrameInfo = + mJITFrameInfoForPreviousJSContexts + ? std::move(mJITFrameInfoForPreviousJSContexts) + : mozilla::MakeUnique<JITFrameInfo>(); + + aBuffer.AddJITInfoForRange(*mBufferPositionWhenReceivedJSContext, + mThreadInfo.ThreadId(), aContext, *jitFrameInfo, + mozilla::ProgressLogger{}); + + mJITFrameInfoForPreviousJSContexts = std::move(jitFrameInfo); + mBufferPositionWhenReceivedJSContext = mozilla::Nothing(); +} + +ThreadStreamingContext::ThreadStreamingContext( + ProfiledThreadData& aProfiledThreadData, const ProfileBuffer& aBuffer, + JSContext* aCx, mozilla::FailureLatch& aFailureLatch, + ProfilerCodeAddressService* aService, + mozilla::ProgressLogger aProgressLogger) + : mProfiledThreadData(aProfiledThreadData), + mJSContext(aCx), + mSamplesDataWriter(aFailureLatch), + mMarkersDataWriter(aFailureLatch), + mUniqueStacks(mProfiledThreadData.PrepareUniqueStacks( + aBuffer, aCx, aFailureLatch, aService, + aProgressLogger.CreateSubLoggerFromTo( + 0_pc, "Preparing thread streaming context unique stacks...", + 99_pc, "Prepared thread streaming context Unique stacks"))) { + if (aFailureLatch.Failed()) { + return; + } + mSamplesDataWriter.SetUniqueStrings(mUniqueStacks->UniqueStrings()); + mSamplesDataWriter.StartBareList(); + mMarkersDataWriter.SetUniqueStrings(mUniqueStacks->UniqueStrings()); + mMarkersDataWriter.StartBareList(); +} + +void ThreadStreamingContext::FinalizeWriter() { + mSamplesDataWriter.EndBareList(); + mMarkersDataWriter.EndBareList(); +} + +ProcessStreamingContext::ProcessStreamingContext( + size_t aThreadCount, mozilla::FailureLatch& aFailureLatch, + const mozilla::TimeStamp& aProcessStartTime, double aSinceTime) + : mFailureLatch(aFailureLatch), + mProcessStartTime(aProcessStartTime), + mSinceTime(aSinceTime) { + if (mFailureLatch.Failed()) { + return; + } + if (!mTIDList.initCapacity(aThreadCount)) { + mFailureLatch.SetFailure( + "OOM in ProcessStreamingContext allocating TID list"); + return; + } + if (!mThreadStreamingContextList.initCapacity(aThreadCount)) { + mFailureLatch.SetFailure( + "OOM in ProcessStreamingContext allocating context list"); + mTIDList.clear(); + return; + } +} + +ProcessStreamingContext::~ProcessStreamingContext() { + if (mFailureLatch.Failed()) { + return; + } + MOZ_ASSERT(mTIDList.length() == mThreadStreamingContextList.length()); + MOZ_ASSERT(mTIDList.length() == mTIDList.capacity(), + "Didn't pre-allocate exactly right"); +} + +void ProcessStreamingContext::AddThreadStreamingContext( + ProfiledThreadData& aProfiledThreadData, const ProfileBuffer& aBuffer, + JSContext* aCx, ProfilerCodeAddressService* aService, + mozilla::ProgressLogger aProgressLogger) { + if (mFailureLatch.Failed()) { + return; + } + MOZ_ASSERT(mTIDList.length() == mThreadStreamingContextList.length()); + MOZ_ASSERT(mTIDList.length() < mTIDList.capacity(), + "Didn't pre-allocate enough"); + mTIDList.infallibleAppend(aProfiledThreadData.Info().ThreadId()); + mThreadStreamingContextList.infallibleEmplaceBack( + aProfiledThreadData, aBuffer, aCx, mFailureLatch, aService, + aProgressLogger.CreateSubLoggerFromTo( + 1_pc, "Prepared streaming thread id", 100_pc, + "Added thread streaming context")); +} |