/* -*- 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 #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> 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>( 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> 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 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( 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(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(profiler_current_process_id().ToNumber())); aWriter.IntProperty("tid", static_cast(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 = mJITFrameInfoForPreviousJSContexts ? std::move(mJITFrameInfoForPreviousJSContexts) : mozilla::MakeUnique(); 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")); }