Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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("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"));
}