diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /docshell/base/timeline | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
29 files changed, 1856 insertions, 0 deletions
diff --git a/docshell/base/timeline/AbstractTimelineMarker.cpp b/docshell/base/timeline/AbstractTimelineMarker.cpp new file mode 100644 index 0000000000..471742089c --- /dev/null +++ b/docshell/base/timeline/AbstractTimelineMarker.cpp @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; 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 "AbstractTimelineMarker.h" + +#include "mozilla/TimeStamp.h" +#include "MainThreadUtils.h" +#include "nsAppRunner.h" + +namespace mozilla { + +AbstractTimelineMarker::AbstractTimelineMarker(const char* aName, + MarkerTracingType aTracingType) + : mName(aName), + mTracingType(aTracingType), + mProcessType(XRE_GetProcessType()), + mIsOffMainThread(!NS_IsMainThread()) { + MOZ_COUNT_CTOR(AbstractTimelineMarker); + SetCurrentTime(); +} + +AbstractTimelineMarker::AbstractTimelineMarker(const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType) + : mName(aName), + mTracingType(aTracingType), + mProcessType(XRE_GetProcessType()), + mIsOffMainThread(!NS_IsMainThread()) { + MOZ_COUNT_CTOR(AbstractTimelineMarker); + SetCustomTime(aTime); +} + +UniquePtr<AbstractTimelineMarker> AbstractTimelineMarker::Clone() { + MOZ_ASSERT(false, "Clone method not yet implemented on this marker type."); + return nullptr; +} + +bool AbstractTimelineMarker::Equals(const AbstractTimelineMarker& aOther) { + // Check whether two markers should be considered the same, for the purpose + // of pairing start and end markers. Normally this definition suffices. + return strcmp(mName, aOther.mName) == 0; +} + +AbstractTimelineMarker::~AbstractTimelineMarker() { + MOZ_COUNT_DTOR(AbstractTimelineMarker); +} + +void AbstractTimelineMarker::SetCurrentTime() { + TimeStamp now = TimeStamp::Now(); + SetCustomTime(now); +} + +void AbstractTimelineMarker::SetCustomTime(const TimeStamp& aTime) { + mTime = (aTime - TimeStamp::ProcessCreation()).ToMilliseconds(); +} + +void AbstractTimelineMarker::SetCustomTime(DOMHighResTimeStamp aTime) { + mTime = aTime; +} + +void AbstractTimelineMarker::SetProcessType(GeckoProcessType aProcessType) { + mProcessType = aProcessType; +} + +void AbstractTimelineMarker::SetOffMainThread(bool aIsOffMainThread) { + mIsOffMainThread = aIsOffMainThread; +} + +} // namespace mozilla diff --git a/docshell/base/timeline/AbstractTimelineMarker.h b/docshell/base/timeline/AbstractTimelineMarker.h new file mode 100644 index 0000000000..8581ba53f8 --- /dev/null +++ b/docshell/base/timeline/AbstractTimelineMarker.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_AbstractTimelineMarker_h_ +#define mozilla_AbstractTimelineMarker_h_ + +#include "TimelineMarkerEnums.h" // for MarkerTracingType +#include "nsDOMNavigationTiming.h" // for DOMHighResTimeStamp +#include "nsXULAppAPI.h" // for GeckoProcessType +#include "mozilla/UniquePtr.h" + +struct JSContext; +class JSObject; + +namespace mozilla { +class TimeStamp; + +namespace dom { +struct ProfileTimelineMarker; +} + +class AbstractTimelineMarker { + private: + AbstractTimelineMarker() = delete; + AbstractTimelineMarker(const AbstractTimelineMarker& aOther) = delete; + void operator=(const AbstractTimelineMarker& aOther) = delete; + + public: + AbstractTimelineMarker(const char* aName, MarkerTracingType aTracingType); + + AbstractTimelineMarker(const char* aName, const TimeStamp& aTime, + MarkerTracingType aTracingType); + + virtual ~AbstractTimelineMarker(); + + virtual UniquePtr<AbstractTimelineMarker> Clone(); + virtual bool Equals(const AbstractTimelineMarker& aOther); + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) = 0; + virtual JSObject* GetStack() = 0; + + const char* GetName() const { return mName; } + DOMHighResTimeStamp GetTime() const { return mTime; } + MarkerTracingType GetTracingType() const { return mTracingType; } + + uint8_t GetProcessType() const { return mProcessType; }; + bool IsOffMainThread() const { return mIsOffMainThread; }; + + private: + const char* mName; + DOMHighResTimeStamp mTime; + MarkerTracingType mTracingType; + + uint8_t mProcessType; // @see `enum GeckoProcessType`. + bool mIsOffMainThread; + + protected: + void SetCurrentTime(); + void SetCustomTime(const TimeStamp& aTime); + void SetCustomTime(DOMHighResTimeStamp aTime); + void SetProcessType(GeckoProcessType aProcessType); + void SetOffMainThread(bool aIsOffMainThread); +}; + +} // namespace mozilla + +#endif /* mozilla_AbstractTimelineMarker_h_ */ diff --git a/docshell/base/timeline/AutoGlobalTimelineMarker.cpp b/docshell/base/timeline/AutoGlobalTimelineMarker.cpp new file mode 100644 index 0000000000..d8814a960d --- /dev/null +++ b/docshell/base/timeline/AutoGlobalTimelineMarker.cpp @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; 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 "AutoGlobalTimelineMarker.h" + +#include "TimelineConsumers.h" +#include "MainThreadUtils.h" + +namespace mozilla { + +AutoGlobalTimelineMarker::AutoGlobalTimelineMarker( + const char* aName, MarkerStackRequest aStackRequest /* = STACK */ + ) + : mName(aName), mStackRequest(aStackRequest) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || timelines->IsEmpty()) { + return; + } + + timelines->AddMarkerForAllObservedDocShells(mName, MarkerTracingType::START, + mStackRequest); +} + +AutoGlobalTimelineMarker::~AutoGlobalTimelineMarker() { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || timelines->IsEmpty()) { + return; + } + + timelines->AddMarkerForAllObservedDocShells(mName, MarkerTracingType::END, + mStackRequest); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/AutoGlobalTimelineMarker.h b/docshell/base/timeline/AutoGlobalTimelineMarker.h new file mode 100644 index 0000000000..a9bbc92fce --- /dev/null +++ b/docshell/base/timeline/AutoGlobalTimelineMarker.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_AutoGlobalTimelineMarker_h_ +#define mozilla_AutoGlobalTimelineMarker_h_ + +#include "mozilla/Attributes.h" +#include "TimelineMarkerEnums.h" + +namespace mozilla { + +// # AutoGlobalTimelineMarker +// +// Similar to `AutoTimelineMarker`, but adds its traced marker to all docshells, +// not a single particular one. This is useful for operations that aren't +// associated with any one particular doc shell, or when it isn't clear which +// docshell triggered the operation. +// +// Example usage: +// +// { +// AutoGlobalTimelineMarker marker("Cycle Collection"); +// nsCycleCollector* cc = GetCycleCollector(); +// cc->Collect(); +// ... +// } +class MOZ_RAII AutoGlobalTimelineMarker { + // The name of the marker we are adding. + const char* mName; + // Whether to capture the JS stack or not. + MarkerStackRequest mStackRequest; + + public: + explicit AutoGlobalTimelineMarker( + const char* aName, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + ~AutoGlobalTimelineMarker(); + + AutoGlobalTimelineMarker(const AutoGlobalTimelineMarker& aOther) = delete; + void operator=(const AutoGlobalTimelineMarker& aOther) = delete; +}; + +} // namespace mozilla + +#endif /* mozilla_AutoGlobalTimelineMarker_h_ */ diff --git a/docshell/base/timeline/AutoRestyleTimelineMarker.cpp b/docshell/base/timeline/AutoRestyleTimelineMarker.cpp new file mode 100644 index 0000000000..c012dfbc17 --- /dev/null +++ b/docshell/base/timeline/AutoRestyleTimelineMarker.cpp @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; 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 "AutoRestyleTimelineMarker.h" + +#include "TimelineConsumers.h" +#include "MainThreadUtils.h" +#include "nsIDocShell.h" +#include "RestyleTimelineMarker.h" + +namespace mozilla { + +AutoRestyleTimelineMarker::AutoRestyleTimelineMarker(nsIDocShell* aDocShell, + bool aIsAnimationOnly) + : mDocShell(nullptr), mIsAnimationOnly(aIsAnimationOnly) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!aDocShell) { + return; + } + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || !timelines->HasConsumer(aDocShell)) { + return; + } + + mDocShell = aDocShell; + timelines->AddMarkerForDocShell( + mDocShell, MakeUnique<RestyleTimelineMarker>(mIsAnimationOnly, + MarkerTracingType::START)); +} + +AutoRestyleTimelineMarker::~AutoRestyleTimelineMarker() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mDocShell) { + return; + } + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || !timelines->HasConsumer(mDocShell)) { + return; + } + + timelines->AddMarkerForDocShell( + mDocShell, MakeUnique<RestyleTimelineMarker>(mIsAnimationOnly, + MarkerTracingType::END)); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/AutoRestyleTimelineMarker.h b/docshell/base/timeline/AutoRestyleTimelineMarker.h new file mode 100644 index 0000000000..1246e1a9bd --- /dev/null +++ b/docshell/base/timeline/AutoRestyleTimelineMarker.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_AutoRestyleTimelineMarker_h_ +#define mozilla_AutoRestyleTimelineMarker_h_ + +#include "mozilla/RefPtr.h" + +class nsIDocShell; + +namespace mozilla { + +class MOZ_RAII AutoRestyleTimelineMarker { + RefPtr<nsIDocShell> mDocShell; + bool mIsAnimationOnly; + + public: + AutoRestyleTimelineMarker(nsIDocShell* aDocShell, bool aIsAnimationOnly); + ~AutoRestyleTimelineMarker(); + + AutoRestyleTimelineMarker(const AutoRestyleTimelineMarker& aOther) = delete; + void operator=(const AutoRestyleTimelineMarker& aOther) = delete; +}; + +} // namespace mozilla + +#endif /* mozilla_AutoRestyleTimelineMarker_h_ */ diff --git a/docshell/base/timeline/AutoTimelineMarker.cpp b/docshell/base/timeline/AutoTimelineMarker.cpp new file mode 100644 index 0000000000..714b818f0a --- /dev/null +++ b/docshell/base/timeline/AutoTimelineMarker.cpp @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; 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 "AutoTimelineMarker.h" + +#include "nsIDocShell.h" +#include "TimelineConsumers.h" +#include "MainThreadUtils.h" + +namespace mozilla { + +AutoTimelineMarker::AutoTimelineMarker(nsIDocShell* aDocShell, + const char* aName) + : mName(aName), mDocShell(nullptr) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!aDocShell) { + return; + } + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || !timelines->HasConsumer(aDocShell)) { + return; + } + + mDocShell = aDocShell; + timelines->AddMarkerForDocShell(mDocShell, mName, MarkerTracingType::START); +} + +AutoTimelineMarker::~AutoTimelineMarker() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mDocShell) { + return; + } + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || !timelines->HasConsumer(mDocShell)) { + return; + } + + timelines->AddMarkerForDocShell(mDocShell, mName, MarkerTracingType::END); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/AutoTimelineMarker.h b/docshell/base/timeline/AutoTimelineMarker.h new file mode 100644 index 0000000000..a86c741bb0 --- /dev/null +++ b/docshell/base/timeline/AutoTimelineMarker.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_AutoTimelineMarker_h_ +#define mozilla_AutoTimelineMarker_h_ + +#include "mozilla/RefPtr.h" + +class nsIDocShell; + +namespace mozilla { + +// # AutoTimelineMarker +// +// An RAII class to trace some task in the platform by adding a start and end +// timeline marker pair. These markers are then rendered in the devtools' +// performance tool's waterfall graph. +// +// Example usage: +// +// { +// AutoTimelineMarker marker(mDocShell, "Parse CSS"); +// nsresult rv = ParseTheCSSFile(mFile); +// ... +// } +class MOZ_RAII AutoTimelineMarker { + // The name of the marker we are adding. + const char* mName; + + // The docshell that is associated with this marker. + RefPtr<nsIDocShell> mDocShell; + + public: + AutoTimelineMarker(nsIDocShell* aDocShell, const char* aName); + ~AutoTimelineMarker(); + + AutoTimelineMarker(const AutoTimelineMarker& aOther) = delete; + void operator=(const AutoTimelineMarker& aOther) = delete; +}; + +} // namespace mozilla + +#endif /* mozilla_AutoTimelineMarker_h_ */ diff --git a/docshell/base/timeline/CompositeTimelineMarker.h b/docshell/base/timeline/CompositeTimelineMarker.h new file mode 100644 index 0000000000..cd2896fefa --- /dev/null +++ b/docshell/base/timeline/CompositeTimelineMarker.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_CompositeTimelineMarker_h_ +#define mozilla_CompositeTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class CompositeTimelineMarker : public TimelineMarker { + public: + CompositeTimelineMarker(const TimeStamp& aTime, + MarkerTracingType aTracingType) + : TimelineMarker("Composite", aTime, aTracingType) { + // Even though these markers end up being created on the main thread in the + // content or chrome processes, they actually trace down code in the + // compositor parent process. All the information for creating these markers + // is sent along via IPC to an nsView when a composite finishes. + // Mark this as 'off the main thread' to style it differently in frontends. + SetOffMainThread(true); + } +}; + +} // namespace mozilla + +#endif // mozilla_CompositeTimelineMarker_h_ diff --git a/docshell/base/timeline/ConsoleTimelineMarker.h b/docshell/base/timeline/ConsoleTimelineMarker.h new file mode 100644 index 0000000000..232aa1a60e --- /dev/null +++ b/docshell/base/timeline/ConsoleTimelineMarker.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_ConsoleTimelineMarker_h_ +#define mozilla_ConsoleTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class ConsoleTimelineMarker : public TimelineMarker { + public: + ConsoleTimelineMarker(const nsAString& aCause, MarkerTracingType aTracingType) + : TimelineMarker("ConsoleTime", aTracingType), mCause(aCause) { + // Stack is captured by default on the "start" marker. Explicitly also + // capture stack on the "end" marker. + if (aTracingType == MarkerTracingType::END) { + CaptureStack(); + } + } + + virtual bool Equals(const AbstractTimelineMarker& aOther) override { + if (!TimelineMarker::Equals(aOther)) { + return false; + } + // Console markers must have matching causes as well. It is safe to perform + // a static_cast here as the previous equality check ensures that this is + // a console marker instance. + return mCause == static_cast<const ConsoleTimelineMarker*>(&aOther)->mCause; + } + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mCauseName.Construct(mCause); + } else { + aMarker.mEndStack = GetStack(); + } + } + + private: + nsString mCause; +}; + +} // namespace mozilla + +#endif // mozilla_ConsoleTimelineMarker_h_ diff --git a/docshell/base/timeline/DocLoadingTimelineMarker.h b/docshell/base/timeline/DocLoadingTimelineMarker.h new file mode 100644 index 0000000000..8f6a7db780 --- /dev/null +++ b/docshell/base/timeline/DocLoadingTimelineMarker.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_DocLoadingTimelineMarker_h_ +#define mozilla_DocLoadingTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class DocLoadingTimelineMarker : public TimelineMarker { + public: + explicit DocLoadingTimelineMarker(const char* aName) + : TimelineMarker(aName, MarkerTracingType::TIMESTAMP), + mUnixTime(PR_Now()) {} + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + aMarker.mUnixTime.Construct(mUnixTime); + } + + private: + // Certain consumers might use Date.now() or similar for tracing time. + // However, TimelineMarkers use process creation as an epoch, which provides + // more precision. To allow syncing, attach an additional unix timestamp. + // Using this instead of `AbstractTimelineMarker::GetTime()'s` timestamp + // is strongly discouraged. + PRTime mUnixTime; +}; + +} // namespace mozilla + +#endif // mozilla_DocLoadingTimelineMarker_h_ diff --git a/docshell/base/timeline/EventTimelineMarker.h b/docshell/base/timeline/EventTimelineMarker.h new file mode 100644 index 0000000000..5e5bc5c4a6 --- /dev/null +++ b/docshell/base/timeline/EventTimelineMarker.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_EventTimelineMarker_h_ +#define mozilla_EventTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class EventTimelineMarker : public TimelineMarker { + public: + EventTimelineMarker(const nsAString& aType, uint16_t aPhase, + MarkerTracingType aTracingType) + : TimelineMarker("DOMEvent", aTracingType), + mType(aType), + mPhase(aPhase) {} + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mType.Construct(mType); + aMarker.mEventPhase.Construct(mPhase); + } + } + + private: + nsString mType; + uint16_t mPhase; +}; + +} // namespace mozilla + +#endif // mozilla_EventTimelineMarker_h_ diff --git a/docshell/base/timeline/JavascriptTimelineMarker.h b/docshell/base/timeline/JavascriptTimelineMarker.h new file mode 100644 index 0000000000..0e07f3b605 --- /dev/null +++ b/docshell/base/timeline/JavascriptTimelineMarker.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_JavascriptTimelineMarker_h_ +#define mozilla_JavascriptTimelineMarker_h_ + +#include "TimelineMarker.h" + +#include "mozilla/Maybe.h" + +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/ToJSValue.h" + +namespace mozilla { + +class JavascriptTimelineMarker : public TimelineMarker { + public: + // The caller owns |aAsyncCause| here, so we must copy it into a separate + // string for use later on. + JavascriptTimelineMarker(const char* aReason, const nsAString& aFunctionName, + const nsAString& aFileName, uint32_t aLineNumber, + MarkerTracingType aTracingType, + JS::Handle<JS::Value> aAsyncStack, + const char* aAsyncCause) + : TimelineMarker("Javascript", aTracingType, + MarkerStackRequest::NO_STACK), + mCause(NS_ConvertUTF8toUTF16(aReason)), + mFunctionName(aFunctionName), + mFileName(aFileName), + mLineNumber(aLineNumber), + mAsyncCause(aAsyncCause) { + JSContext* ctx = nsContentUtils::GetCurrentJSContext(); + if (ctx) { + mAsyncStack.init(ctx, aAsyncStack); + } + } + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + aMarker.mCauseName.Construct(mCause); + + if (!mFunctionName.IsEmpty() || !mFileName.IsEmpty()) { + dom::RootedDictionary<dom::ProfileTimelineStackFrame> stackFrame(aCx); + stackFrame.mLine.Construct(mLineNumber); + stackFrame.mSource.Construct(mFileName); + stackFrame.mFunctionDisplayName.Construct(mFunctionName); + + if (mAsyncStack.isObject() && !mAsyncCause.IsEmpty()) { + JS::Rooted<JSObject*> asyncStack(aCx, &mAsyncStack.toObject()); + JS::Rooted<JSObject*> parentFrame(aCx); + JS::Rooted<JSString*> asyncCause( + aCx, JS_NewUCStringCopyN(aCx, mAsyncCause.BeginReading(), + mAsyncCause.Length())); + if (!asyncCause) { + JS_ClearPendingException(aCx); + return; + } + + if (JS::IsMaybeWrappedSavedFrame(asyncStack) && + !JS::CopyAsyncStack(aCx, asyncStack, asyncCause, &parentFrame, + mozilla::Nothing())) { + JS_ClearPendingException(aCx); + } else { + stackFrame.mAsyncParent = parentFrame; + } + } + + JS::Rooted<JS::Value> newStack(aCx); + if (ToJSValue(aCx, stackFrame, &newStack)) { + if (newStack.isObject()) { + aMarker.mStack = &newStack.toObject(); + } + } else { + JS_ClearPendingException(aCx); + } + } + } + + private: + nsString mCause; + nsString mFunctionName; + nsString mFileName; + uint32_t mLineNumber; + JS::PersistentRooted<JS::Value> mAsyncStack; + NS_ConvertUTF8toUTF16 mAsyncCause; +}; + +} // namespace mozilla + +#endif // mozilla_JavascriptTimelineMarker_h_ diff --git a/docshell/base/timeline/LayerTimelineMarker.h b/docshell/base/timeline/LayerTimelineMarker.h new file mode 100644 index 0000000000..7c7336ba52 --- /dev/null +++ b/docshell/base/timeline/LayerTimelineMarker.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_LayerTimelineMarker_h_ +#define mozilla_LayerTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/mozalloc_oom.h" +#include "nsRegion.h" + +namespace mozilla { + +class LayerTimelineMarker : public TimelineMarker { + public: + explicit LayerTimelineMarker(const nsIntRegion& aRegion) + : TimelineMarker("Layer", MarkerTracingType::HELPER_EVENT), + mRegion(aRegion) {} + + void AddLayerRectangles( + dom::Sequence<dom::ProfileTimelineLayerRect>& aRectangles) { + for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) { + const nsIntRect& iterRect = iter.Get(); + dom::ProfileTimelineLayerRect rect; + rect.mX = iterRect.X(); + rect.mY = iterRect.Y(); + rect.mWidth = iterRect.Width(); + rect.mHeight = iterRect.Height(); + if (!aRectangles.AppendElement(rect, fallible)) { + // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might + // involve multiple reallocations) and potentially crashing here, + // SetCapacity could be called outside the loop once. + mozalloc_handle_oom(0); + } + } + } + + private: + nsIntRegion mRegion; +}; + +} // namespace mozilla + +#endif // mozilla_LayerTimelineMarker_h_ diff --git a/docshell/base/timeline/MarkersStorage.cpp b/docshell/base/timeline/MarkersStorage.cpp new file mode 100644 index 0000000000..4a84135f29 --- /dev/null +++ b/docshell/base/timeline/MarkersStorage.cpp @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 8; 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 "MarkersStorage.h" +#include "MainThreadUtils.h" + +namespace mozilla { + +MarkersStorage::MarkersStorage(const char* aMutexName) : mLock(aMutexName) { + MOZ_ASSERT(NS_IsMainThread()); +} + +MarkersStorage::~MarkersStorage() { MOZ_ASSERT(NS_IsMainThread()); } + +Mutex& MarkersStorage::GetLock() { return mLock; } + +} // namespace mozilla diff --git a/docshell/base/timeline/MarkersStorage.h b/docshell/base/timeline/MarkersStorage.h new file mode 100644 index 0000000000..746a02e4ec --- /dev/null +++ b/docshell/base/timeline/MarkersStorage.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_MarkersStorage_h_ +#define mozilla_MarkersStorage_h_ + +#include "TimelineMarkerEnums.h" // for MarkerReleaseRequest +#include "mozilla/Mutex.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/LinkedList.h" +#include "nsTArray.h" + +namespace mozilla { +class AbstractTimelineMarker; + +namespace dom { +struct ProfileTimelineMarker; +} + +class MarkersStorage : public LinkedListElement<MarkersStorage> { + private: + MarkersStorage() = delete; + MarkersStorage(const MarkersStorage& aOther) = delete; + void operator=(const MarkersStorage& aOther) = delete; + + public: + explicit MarkersStorage(const char* aMutexName); + virtual ~MarkersStorage(); + + virtual void AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) = 0; + virtual void AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) = 0; + virtual void ClearMarkers() = 0; + virtual void PopMarkers(JSContext* aCx, + nsTArray<dom::ProfileTimelineMarker>& aStore) = 0; + + protected: + Mutex& GetLock(); + + private: + Mutex mLock; +}; + +} // namespace mozilla + +#endif /* mozilla_MarkersStorage_h_ */ diff --git a/docshell/base/timeline/MessagePortTimelineMarker.h b/docshell/base/timeline/MessagePortTimelineMarker.h new file mode 100644 index 0000000000..c6d91fa7eb --- /dev/null +++ b/docshell/base/timeline/MessagePortTimelineMarker.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_MessagePortTimelineMarker_h_ +#define mozilla_MessagePortTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class MessagePortTimelineMarker : public TimelineMarker { + public: + MessagePortTimelineMarker( + dom::ProfileTimelineMessagePortOperationType aOperationType, + MarkerTracingType aTracingType) + : TimelineMarker("MessagePort", aTracingType, + MarkerStackRequest::NO_STACK), + mOperationType(aOperationType) {} + + virtual UniquePtr<AbstractTimelineMarker> Clone() override { + MessagePortTimelineMarker* clone = + new MessagePortTimelineMarker(mOperationType, GetTracingType()); + clone->SetCustomTime(GetTime()); + return UniquePtr<AbstractTimelineMarker>(clone); + } + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mMessagePortOperation.Construct(mOperationType); + } + } + + private: + dom::ProfileTimelineMessagePortOperationType mOperationType; +}; + +} // namespace mozilla + +#endif /* mozilla_MessagePortTimelineMarker_h_ */ diff --git a/docshell/base/timeline/ObservedDocShell.cpp b/docshell/base/timeline/ObservedDocShell.cpp new file mode 100644 index 0000000000..4cba68ad29 --- /dev/null +++ b/docshell/base/timeline/ObservedDocShell.cpp @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 8; 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 "ObservedDocShell.h" + +#include <utility> + +#include "AbstractTimelineMarker.h" +#include "LayerTimelineMarker.h" +#include "MainThreadUtils.h" +#include "mozilla/AutoRestore.h" +#include "nsIDocShell.h" + +namespace mozilla { + +ObservedDocShell::ObservedDocShell(nsIDocShell* aDocShell) + : MarkersStorage("ObservedDocShellMutex"), + mDocShell(aDocShell), + mPopping(false) { + MOZ_ASSERT(NS_IsMainThread()); +} + +void ObservedDocShell::AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) { + // Only allow main thread markers to go into this list. No need to lock + // here since `mTimelineMarkers` will only be accessed or modified on the + // main thread only. + MOZ_ASSERT(NS_IsMainThread()); + // Don't accept any markers generated by the process of popping + // markers. + if (!mPopping) { + mTimelineMarkers.AppendElement(std::move(aMarker)); + } +} + +void ObservedDocShell::AddOTMTMarker( + UniquePtr<AbstractTimelineMarker>&& aMarker) { + // Only allow off the main thread markers to go into this list. Since most + // of our markers come from the main thread, be a little more efficient and + // avoid dealing with multithreading scenarios until all the markers are + // actually cleared or popped in `ClearMarkers` or `PopMarkers`. + MOZ_ASSERT(!NS_IsMainThread()); + MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`. + mOffTheMainThreadTimelineMarkers.AppendElement(std::move(aMarker)); +} + +void ObservedDocShell::ClearMarkers() { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`. + mTimelineMarkers.Clear(); + mOffTheMainThreadTimelineMarkers.Clear(); +} + +void ObservedDocShell::PopMarkers( + JSContext* aCx, nsTArray<dom::ProfileTimelineMarker>& aStore) { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`. + + MOZ_RELEASE_ASSERT(!mPopping); + AutoRestore<bool> resetPopping(mPopping); + mPopping = true; + + // First, move all of our markers into a single array. We'll chose + // the `mTimelineMarkers` store because that's where we expect most of + // our markers to be. + mTimelineMarkers.AppendElements(std::move(mOffTheMainThreadTimelineMarkers)); + + // If we see an unpaired START, we keep it around for the next call + // to ObservedDocShell::PopMarkers. We store the kept START objects here. + nsTArray<UniquePtr<AbstractTimelineMarker>> keptStartMarkers; + + for (uint32_t i = 0; i < mTimelineMarkers.Length(); ++i) { + UniquePtr<AbstractTimelineMarker>& startPayload = + mTimelineMarkers.ElementAt(i); + + // If this is a TIMESTAMP marker, there's no corresponding END, + // as it's a single unit of time, not a duration. + if (startPayload->GetTracingType() == MarkerTracingType::TIMESTAMP) { + dom::ProfileTimelineMarker* marker = aStore.AppendElement(); + marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName()); + marker->mStart = startPayload->GetTime(); + marker->mEnd = startPayload->GetTime(); + marker->mStack = startPayload->GetStack(); + startPayload->AddDetails(aCx, *marker); + continue; + } + + // Whenever a START marker is found, look for the corresponding END + // and build a {name,start,end} JS object. + if (startPayload->GetTracingType() == MarkerTracingType::START) { + bool hasSeenEnd = false; + + // "Paint" markers are different because painting is handled at root + // docshell level. The information that a paint was done is stored at + // sub-docshell level, but we can only be sure that a paint did actually + // happen in if a "Layer" marker was recorded too. + bool startIsPaintType = strcmp(startPayload->GetName(), "Paint") == 0; + bool hasSeenLayerType = false; + + // If we are processing a "Paint" marker, we append information from + // all the embedded "Layer" markers to this array. + dom::Sequence<dom::ProfileTimelineLayerRect> layerRectangles; + + // DOM events can be nested, so we must take care when searching + // for the matching end. It doesn't hurt to apply this logic to + // all event types. + uint32_t markerDepth = 0; + + // The assumption is that the devtools timeline flushes markers frequently + // enough for the amount of markers to always be small enough that the + // nested for loop isn't going to be a performance problem. + for (uint32_t j = i + 1; j < mTimelineMarkers.Length(); ++j) { + UniquePtr<AbstractTimelineMarker>& endPayload = + mTimelineMarkers.ElementAt(j); + bool endIsLayerType = strcmp(endPayload->GetName(), "Layer") == 0; + + // Look for "Layer" markers to stream out "Paint" markers. + if (startIsPaintType && endIsLayerType) { + AbstractTimelineMarker* raw = endPayload.get(); + LayerTimelineMarker* layerPayload = + static_cast<LayerTimelineMarker*>(raw); + layerPayload->AddLayerRectangles(layerRectangles); + hasSeenLayerType = true; + } + if (!startPayload->Equals(*endPayload)) { + continue; + } + if (endPayload->GetTracingType() == MarkerTracingType::START) { + ++markerDepth; + continue; + } + if (endPayload->GetTracingType() == MarkerTracingType::END) { + if (markerDepth > 0) { + --markerDepth; + continue; + } + if (!startIsPaintType || (startIsPaintType && hasSeenLayerType)) { + dom::ProfileTimelineMarker* marker = aStore.AppendElement(); + marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName()); + marker->mStart = startPayload->GetTime(); + marker->mEnd = endPayload->GetTime(); + marker->mStack = startPayload->GetStack(); + if (hasSeenLayerType) { + marker->mRectangles.Construct(layerRectangles); + } + startPayload->AddDetails(aCx, *marker); + endPayload->AddDetails(aCx, *marker); + } + hasSeenEnd = true; + break; + } + } + + // If we did not see the corresponding END, keep the START. + if (!hasSeenEnd) { + keptStartMarkers.AppendElement( + std::move(mTimelineMarkers.ElementAt(i))); + mTimelineMarkers.RemoveElementAt(i); + --i; + } + } + } + + mTimelineMarkers = std::move(keptStartMarkers); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/ObservedDocShell.h b/docshell/base/timeline/ObservedDocShell.h new file mode 100644 index 0000000000..88b6747efb --- /dev/null +++ b/docshell/base/timeline/ObservedDocShell.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_ObservedDocShell_h_ +#define mozilla_ObservedDocShell_h_ + +#include "MarkersStorage.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + +class nsIDocShell; + +namespace mozilla { +class AbstractTimelineMarker; + +namespace dom { +struct ProfileTimelineMarker; +} + +// # ObservedDocShell +// +// A wrapper around a docshell for which docshell-specific markers are +// allowed to exist. See TimelineConsumers for register/unregister logic. +class ObservedDocShell : public MarkersStorage { + private: + RefPtr<nsIDocShell> mDocShell; + + // Main thread only. + nsTArray<UniquePtr<AbstractTimelineMarker>> mTimelineMarkers; + bool mPopping; + + // Off the main thread only. + nsTArray<UniquePtr<AbstractTimelineMarker>> mOffTheMainThreadTimelineMarkers; + + public: + explicit ObservedDocShell(nsIDocShell* aDocShell); + nsIDocShell* operator*() const { return mDocShell.get(); } + + void AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) override; + void AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) override; + void ClearMarkers() override; + void PopMarkers(JSContext* aCx, + nsTArray<dom::ProfileTimelineMarker>& aStore) override; +}; + +} // namespace mozilla + +#endif /* mozilla_ObservedDocShell_h_ */ diff --git a/docshell/base/timeline/RestyleTimelineMarker.h b/docshell/base/timeline/RestyleTimelineMarker.h new file mode 100644 index 0000000000..4cce10d94b --- /dev/null +++ b/docshell/base/timeline/RestyleTimelineMarker.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_RestyleTimelineMarker_h_ +#define mozilla_RestyleTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class RestyleTimelineMarker : public TimelineMarker { + public: + RestyleTimelineMarker(bool aIsAnimationOnly, MarkerTracingType aTracingType) + : TimelineMarker("Styles", aTracingType) { + mIsAnimationOnly = aIsAnimationOnly; + } + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mIsAnimationOnly.Construct(mIsAnimationOnly); + } + } + + private: + bool mIsAnimationOnly; +}; + +} // namespace mozilla + +#endif // mozilla_RestyleTimelineMarker_h_ diff --git a/docshell/base/timeline/TimelineConsumers.cpp b/docshell/base/timeline/TimelineConsumers.cpp new file mode 100644 index 0000000000..805a54979c --- /dev/null +++ b/docshell/base/timeline/TimelineConsumers.cpp @@ -0,0 +1,287 @@ +/* -*- Mode: C++; tab-width: 8; 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 "TimelineConsumers.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/ObservedDocShell.h" +#include "mozilla/Services.h" +#include "mozilla/TimelineMarker.h" +#include "jsapi.h" +#include "nsAppRunner.h" // for XRE_IsContentProcess, XRE_IsParentProcess +#include "nsCRT.h" +#include "nsDocShell.h" +#include "nsIObserverService.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(TimelineConsumers, nsIObserver); + +StaticMutex TimelineConsumers::sMutex; + +// Manually manage this singleton's lifetime and destroy it before shutdown. +// This avoids the leakchecker detecting false-positive memory leaks when +// using automatic memory management (i.e. statically instantiating this +// singleton inside the `Get` method), which would automatically destroy it on +// application shutdown, but too late for the leakchecker. Sigh... +StaticRefPtr<TimelineConsumers> TimelineConsumers::sInstance; + +// This flag makes sure the singleton never gets instantiated while a shutdown +// is in progress. This can actually happen, and `ClearOnShutdown` doesn't work +// in these cases. +bool TimelineConsumers::sInShutdown = false; + +already_AddRefed<TimelineConsumers> TimelineConsumers::Get() { + // Using this class is not supported yet for other processes other than + // parent or content. To avoid accidental checks to methods like `IsEmpty`, + // which would probably always be true in those cases, assert here. + // Remember, there will be different singletons available to each process. + + // TODO: we have to avoid calling this function in socket process. + MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsParentProcess()); + + // If we are shutting down, don't bother doing anything. Note: we can only + // know whether or not we're in shutdown if we're instantiated. + if (sInShutdown) { + return nullptr; + } + + // Note: We don't simply check `sInstance` for null-ness here, since otherwise + // this can resurrect the TimelineConsumers pretty late during shutdown. + // We won't know if we're in shutdown or not though, because the singleton + // could have been destroyed or just never instantiated, so in the previous + // conditional `sInShutdown` would be false. + static bool firstTime = true; + if (firstTime) { + firstTime = false; + + StaticMutexAutoLock lock(sMutex); + sInstance = new TimelineConsumers(); + + // Make sure the initialization actually suceeds, otherwise don't allow + // access by destroying the instance immediately. + if (sInstance->Init()) { + ClearOnShutdown(&sInstance); + } else { + sInstance->RemoveObservers(); + sInstance = nullptr; + } + } + + RefPtr<TimelineConsumers> copy = sInstance.get(); + return copy.forget(); +} + +bool TimelineConsumers::Init() { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (!obs) { + return false; + } + if (NS_WARN_IF(NS_FAILED( + obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)))) { + return false; + } + return true; +} + +bool TimelineConsumers::RemoveObservers() { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (!obs) { + return false; + } + if (NS_WARN_IF(NS_FAILED( + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)))) { + return false; + } + return true; +} + +nsresult TimelineConsumers::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + sInShutdown = true; + RemoveObservers(); + return NS_OK; + } + + MOZ_ASSERT(false, "TimelineConsumers got unexpected topic!"); + return NS_ERROR_UNEXPECTED; +} + +TimelineConsumers::TimelineConsumers() : mActiveConsumers(0) {} + +void TimelineConsumers::AddConsumer(nsDocShell* aDocShell) { + MOZ_ASSERT(NS_IsMainThread()); + StaticMutexAutoLock lock( + sMutex); // for `mActiveConsumers` and `mMarkersStores`. + + UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved; + MOZ_ASSERT(!observed); + + if (mActiveConsumers == 0) { + JS::SetProfileTimelineRecordingEnabled(true); + } + mActiveConsumers++; + + ObservedDocShell* obsDocShell = new ObservedDocShell(aDocShell); + MarkersStorage* storage = static_cast<MarkersStorage*>(obsDocShell); + + observed.reset(obsDocShell); + mMarkersStores.insertFront(storage); +} + +void TimelineConsumers::RemoveConsumer(nsDocShell* aDocShell) { + MOZ_ASSERT(NS_IsMainThread()); + StaticMutexAutoLock lock( + sMutex); // for `mActiveConsumers` and `mMarkersStores`. + + UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved; + MOZ_ASSERT(observed); + + mActiveConsumers--; + if (mActiveConsumers == 0) { + JS::SetProfileTimelineRecordingEnabled(false); + } + + // Clear all markers from the `mTimelineMarkers` store. + observed.get()->ClearMarkers(); + // Remove self from the `mMarkersStores` store. + observed.get()->remove(); + // Prepare for becoming a consumer later. + observed.reset(nullptr); +} + +bool TimelineConsumers::HasConsumer(nsIDocShell* aDocShell) { + MOZ_ASSERT(NS_IsMainThread()); + return aDocShell ? aDocShell->GetRecordProfileTimelineMarkers() : false; +} + +bool TimelineConsumers::IsEmpty() { + StaticMutexAutoLock lock(sMutex); // for `mActiveConsumers`. + return mActiveConsumers == 0; +} + +void TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell, + const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) { + MOZ_ASSERT(NS_IsMainThread()); + if (HasConsumer(aDocShell)) { + aDocShell->mObserved->AddMarker( + MakeUnique<TimelineMarker>(aName, aTracingType, aStackRequest)); + } +} + +void TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell, + const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) { + MOZ_ASSERT(NS_IsMainThread()); + if (HasConsumer(aDocShell)) { + aDocShell->mObserved->AddMarker( + MakeUnique<TimelineMarker>(aName, aTime, aTracingType, aStackRequest)); + } +} + +void TimelineConsumers::AddMarkerForDocShell( + nsDocShell* aDocShell, UniquePtr<AbstractTimelineMarker>&& aMarker) { + MOZ_ASSERT(NS_IsMainThread()); + if (HasConsumer(aDocShell)) { + aDocShell->mObserved->AddMarker(std::move(aMarker)); + } +} + +void TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell, + const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) { + MOZ_ASSERT(NS_IsMainThread()); + AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aName, aTracingType, + aStackRequest); +} + +void TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell, + const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) { + MOZ_ASSERT(NS_IsMainThread()); + AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aName, aTime, + aTracingType, aStackRequest); +} + +void TimelineConsumers::AddMarkerForDocShell( + nsIDocShell* aDocShell, UniquePtr<AbstractTimelineMarker>&& aMarker) { + MOZ_ASSERT(NS_IsMainThread()); + AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), std::move(aMarker)); +} + +void TimelineConsumers::AddMarkerForAllObservedDocShells( + const char* aName, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest /* = STACK */) { + bool isMainThread = NS_IsMainThread(); + StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`. + + for (MarkersStorage* storage = mMarkersStores.getFirst(); storage != nullptr; + storage = storage->getNext()) { + UniquePtr<AbstractTimelineMarker> marker = + MakeUnique<TimelineMarker>(aName, aTracingType, aStackRequest); + if (isMainThread) { + storage->AddMarker(std::move(marker)); + } else { + storage->AddOTMTMarker(std::move(marker)); + } + } +} + +void TimelineConsumers::AddMarkerForAllObservedDocShells( + const char* aName, const TimeStamp& aTime, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest /* = STACK */) { + bool isMainThread = NS_IsMainThread(); + StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`. + + for (MarkersStorage* storage = mMarkersStores.getFirst(); storage != nullptr; + storage = storage->getNext()) { + UniquePtr<AbstractTimelineMarker> marker = + MakeUnique<TimelineMarker>(aName, aTime, aTracingType, aStackRequest); + if (isMainThread) { + storage->AddMarker(std::move(marker)); + } else { + storage->AddOTMTMarker(std::move(marker)); + } + } +} + +void TimelineConsumers::AddMarkerForAllObservedDocShells( + UniquePtr<AbstractTimelineMarker>& aMarker) { + bool isMainThread = NS_IsMainThread(); + StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`. + + for (MarkersStorage* storage = mMarkersStores.getFirst(); storage != nullptr; + storage = storage->getNext()) { + UniquePtr<AbstractTimelineMarker> clone = aMarker->Clone(); + if (isMainThread) { + storage->AddMarker(std::move(clone)); + } else { + storage->AddOTMTMarker(std::move(clone)); + } + } +} + +void TimelineConsumers::PopMarkers( + nsDocShell* aDocShell, JSContext* aCx, + nsTArray<dom::ProfileTimelineMarker>& aStore) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!aDocShell || !aDocShell->mObserved) { + return; + } + + aDocShell->mObserved->PopMarkers(aCx, aStore); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/TimelineConsumers.h b/docshell/base/timeline/TimelineConsumers.h new file mode 100644 index 0000000000..6a152cfd55 --- /dev/null +++ b/docshell/base/timeline/TimelineConsumers.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_TimelineConsumers_h_ +#define mozilla_TimelineConsumers_h_ + +#include "nsIObserver.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/LinkedList.h" +#include "mozilla/StaticMutex.h" +#include "nsTArray.h" +#include "TimelineMarkerEnums.h" // for MarkerTracingType + +class nsDocShell; +class nsIDocShell; +struct JSContext; + +namespace mozilla { +class TimeStamp; +class MarkersStorage; +class AbstractTimelineMarker; + +namespace dom { +struct ProfileTimelineMarker; +} + +class TimelineConsumers : public nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + private: + TimelineConsumers(); + TimelineConsumers(const TimelineConsumers& aOther) = delete; + void operator=(const TimelineConsumers& aOther) = delete; + virtual ~TimelineConsumers() = default; + + bool Init(); + bool RemoveObservers(); + + public: + static already_AddRefed<TimelineConsumers> Get(); + + // Methods for registering interested consumers (i.e. "devtools toolboxes"). + // Each consumer should be directly focused on a particular docshell, but + // timeline markers don't necessarily have to be tied to that docshell. + // See the public `AddMarker*` methods below. + // Main thread only. + void AddConsumer(nsDocShell* aDocShell); + void RemoveConsumer(nsDocShell* aDocShell); + + bool HasConsumer(nsIDocShell* aDocShell); + + // Checks if there's any existing interested consumer. + // May be called from any thread. + bool IsEmpty(); + + // Methods for adding markers relevant for particular docshells, or generic + // (meaning that they either can't be tied to a particular docshell, or one + // wasn't accessible in the part of the codebase where they're instantiated). + // These will only add markers if at least one docshell is currently being + // observed by a timeline. Markers tied to a particular docshell won't be + // created unless that docshell is specifically being currently observed. + // See nsIDocShell::recordProfileTimelineMarkers + + // These methods create a basic TimelineMarker from a name and some metadata, + // relevant for a specific docshell. + // Main thread only. + void AddMarkerForDocShell( + nsDocShell* aDocShell, const char* aName, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + void AddMarkerForDocShell( + nsIDocShell* aDocShell, const char* aName, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + void AddMarkerForDocShell( + nsDocShell* aDocShell, const char* aName, const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + void AddMarkerForDocShell( + nsIDocShell* aDocShell, const char* aName, const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + // These methods register and receive ownership of an already created marker, + // relevant for a specific docshell. + // Main thread only. + void AddMarkerForDocShell(nsDocShell* aDocShell, + UniquePtr<AbstractTimelineMarker>&& aMarker); + void AddMarkerForDocShell(nsIDocShell* aDocShell, + UniquePtr<AbstractTimelineMarker>&& aMarker); + + // These methods create a basic marker from a name and some metadata, + // which doesn't have to be relevant to a specific docshell. + // May be called from any thread. + void AddMarkerForAllObservedDocShells( + const char* aName, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + void AddMarkerForAllObservedDocShells( + const char* aName, const TimeStamp& aTime, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + // This method clones and registers an already instantiated marker, + // which doesn't have to be relevant to a specific docshell. + // May be called from any thread. + void AddMarkerForAllObservedDocShells( + UniquePtr<AbstractTimelineMarker>& aMarker); + + void PopMarkers(nsDocShell* aDocShell, JSContext* aCx, + nsTArray<dom::ProfileTimelineMarker>& aStore); + + private: + static StaticRefPtr<TimelineConsumers> sInstance; + static bool sInShutdown; + + // Counter for how many timelines are currently interested in markers, + // and a list of the MarkersStorage interfaces representing them. + unsigned long mActiveConsumers; + LinkedList<MarkersStorage> mMarkersStores; + + // Protects this class's data structures. + static StaticMutex sMutex; +}; + +} // namespace mozilla + +#endif /* mozilla_TimelineConsumers_h_ */ diff --git a/docshell/base/timeline/TimelineMarker.cpp b/docshell/base/timeline/TimelineMarker.cpp new file mode 100644 index 0000000000..f43c892a04 --- /dev/null +++ b/docshell/base/timeline/TimelineMarker.cpp @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; 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 "TimelineMarker.h" + +#include "jsapi.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "nsContentUtils.h" + +namespace mozilla { + +TimelineMarker::TimelineMarker(const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) + : AbstractTimelineMarker(aName, aTracingType) { + CaptureStackIfNecessary(aTracingType, aStackRequest); +} + +TimelineMarker::TimelineMarker(const char* aName, const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) + : AbstractTimelineMarker(aName, aTime, aTracingType) { + CaptureStackIfNecessary(aTracingType, aStackRequest); +} + +void TimelineMarker::AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) { + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mProcessType.Construct(GetProcessType()); + aMarker.mIsOffMainThread.Construct(IsOffMainThread()); + } +} + +JSObject* TimelineMarker::GetStack() { + if (mStackTrace.initialized()) { + return mStackTrace; + } + return nullptr; +} + +void TimelineMarker::CaptureStack() { + JSContext* ctx = nsContentUtils::GetCurrentJSContext(); + if (ctx) { + JS::RootedObject stack(ctx); + if (JS::CaptureCurrentStack(ctx, &stack)) { + mStackTrace.init(ctx, stack.get()); + } else { + JS_ClearPendingException(ctx); + } + } +} + +void TimelineMarker::CaptureStackIfNecessary(MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) { + if ((aTracingType == MarkerTracingType::START || + aTracingType == MarkerTracingType::TIMESTAMP) && + aStackRequest != MarkerStackRequest::NO_STACK) { + CaptureStack(); + } +} + +} // namespace mozilla diff --git a/docshell/base/timeline/TimelineMarker.h b/docshell/base/timeline/TimelineMarker.h new file mode 100644 index 0000000000..b7c5a83ce9 --- /dev/null +++ b/docshell/base/timeline/TimelineMarker.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_TimelineMarker_h_ +#define mozilla_TimelineMarker_h_ + +#include "AbstractTimelineMarker.h" +#include "js/RootingAPI.h" + +namespace mozilla { + +// Objects of this type can be added to the timeline if there is an interested +// consumer. The class can also be subclassed to let a given marker creator +// provide custom details. +class TimelineMarker : public AbstractTimelineMarker { + public: + TimelineMarker(const char* aName, MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + TimelineMarker(const char* aName, const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override; + virtual JSObject* GetStack() override; + + protected: + void CaptureStack(); + + private: + // While normally it is not a good idea to make a persistent root, + // in this case changing nsDocShell to participate in cycle + // collection was deemed too invasive, and the markers are only held + // here temporarily to boot. + JS::PersistentRooted<JSObject*> mStackTrace; + + void CaptureStackIfNecessary(MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest); +}; + +} // namespace mozilla + +#endif /* mozilla_TimelineMarker_h_ */ diff --git a/docshell/base/timeline/TimelineMarkerEnums.h b/docshell/base/timeline/TimelineMarkerEnums.h new file mode 100644 index 0000000000..6c6a39245c --- /dev/null +++ b/docshell/base/timeline/TimelineMarkerEnums.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_TimelineMarkerEnums_h_ +#define mozilla_TimelineMarkerEnums_h_ + +namespace mozilla { + +enum class MarkerTracingType { START, END, TIMESTAMP, HELPER_EVENT }; + +enum class MarkerStackRequest { STACK, NO_STACK }; + +} // namespace mozilla + +#endif // mozilla_TimelineMarkerEnums_h_ diff --git a/docshell/base/timeline/TimestampTimelineMarker.h b/docshell/base/timeline/TimestampTimelineMarker.h new file mode 100644 index 0000000000..6950f85899 --- /dev/null +++ b/docshell/base/timeline/TimestampTimelineMarker.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_TimestampTimelineMarker_h_ +#define mozilla_TimestampTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class TimestampTimelineMarker : public TimelineMarker { + public: + explicit TimestampTimelineMarker(const nsAString& aCause) + : TimelineMarker("TimeStamp", MarkerTracingType::TIMESTAMP), + mCause(aCause) {} + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + if (!mCause.IsEmpty()) { + aMarker.mCauseName.Construct(mCause); + } + } + + private: + nsString mCause; +}; + +} // namespace mozilla + +#endif // mozilla_TimestampTimelineMarker_h_ diff --git a/docshell/base/timeline/WorkerTimelineMarker.h b/docshell/base/timeline/WorkerTimelineMarker.h new file mode 100644 index 0000000000..c0c0517b09 --- /dev/null +++ b/docshell/base/timeline/WorkerTimelineMarker.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef mozilla_WorkerTimelineMarker_h_ +#define mozilla_WorkerTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class WorkerTimelineMarker : public TimelineMarker { + public: + WorkerTimelineMarker(dom::ProfileTimelineWorkerOperationType aOperationType, + MarkerTracingType aTracingType) + : TimelineMarker("Worker", aTracingType, MarkerStackRequest::NO_STACK), + mOperationType(aOperationType) {} + + virtual UniquePtr<AbstractTimelineMarker> Clone() override { + WorkerTimelineMarker* clone = + new WorkerTimelineMarker(mOperationType, GetTracingType()); + clone->SetCustomTime(GetTime()); + return UniquePtr<AbstractTimelineMarker>(clone); + } + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mWorkerOperation.Construct(mOperationType); + } + } + + private: + dom::ProfileTimelineWorkerOperationType mOperationType; +}; + +} // namespace mozilla + +#endif /* mozilla_WorkerTimelineMarker_h_ */ diff --git a/docshell/base/timeline/moz.build b/docshell/base/timeline/moz.build new file mode 100644 index 0000000000..9147e8263c --- /dev/null +++ b/docshell/base/timeline/moz.build @@ -0,0 +1,45 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("DevTools", "Performance Tools (Profiler/Timeline)") + +EXPORTS.mozilla += [ + "AbstractTimelineMarker.h", + "AutoGlobalTimelineMarker.h", + "AutoRestyleTimelineMarker.h", + "AutoTimelineMarker.h", + "CompositeTimelineMarker.h", + "ConsoleTimelineMarker.h", + "DocLoadingTimelineMarker.h", + "EventTimelineMarker.h", + "JavascriptTimelineMarker.h", + "LayerTimelineMarker.h", + "MarkersStorage.h", + "MessagePortTimelineMarker.h", + "ObservedDocShell.h", + "RestyleTimelineMarker.h", + "TimelineConsumers.h", + "TimelineMarker.h", + "TimelineMarkerEnums.h", + "TimestampTimelineMarker.h", + "WorkerTimelineMarker.h", +] + +UNIFIED_SOURCES += [ + "AbstractTimelineMarker.cpp", + "AutoGlobalTimelineMarker.cpp", + "AutoRestyleTimelineMarker.cpp", + "AutoTimelineMarker.cpp", + "MarkersStorage.cpp", + "ObservedDocShell.cpp", + "TimelineConsumers.cpp", + "TimelineMarker.cpp", +] + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += ["/docshell/base"] diff --git a/docshell/base/timeline/readme.md b/docshell/base/timeline/readme.md new file mode 100644 index 0000000000..d2c3d5c215 --- /dev/null +++ b/docshell/base/timeline/readme.md @@ -0,0 +1,97 @@ + +#Timeline + +The files in this directory are concerned with providing the backend platform features required for the developer tools interested in tracking down operations done in Gecko. The mechanism we use to define these operations are `markers`. + +Examples of traced operations include: + +* Style Recalculation +* Layout +* Painting +* JavaScript run-to-completion +* HTML parsing +* etc. + +The traced operations are displayed in the DevTools Performance tool's timeline. + +This is an overview of how everything works and can be extended. + +##MarkersStorage +A `MarkersStorage` is an abstract class defining a place where timeline markers may be held. It defines an interface with pure virtual functions to highlight how this storage can be interacted with: + +- `AddMarker`: adding a marker, from the main thread only +- `AddOTMTMarker`: adding a marker off the main thread only +- `ClearMarkers`: clearing all accumulated markers (both from the main thread and off it) +- `PopMarkers`: popping all accumulated markers (both from the main thread and off it). + +Note on why we handle on/off the main thread markers separately: since most of our markers will come from the main thread, we can be a little more efficient and avoid dealing with multithreading scenarios until all the markers are actually cleared or popped in `ClearMarkers` or `PopMarkers`. Main thread markers may only be added via `AddMarker`, while off the main thread markers may only be added via `AddOTMTMarker`. Clearing and popping markers will yield until all operations involving off the main thread markers finish. When popping, the markers accumulated off the main thread will be moved over. We expect popping to be fairly infrequent (every few hundred milliseconds, currently we schedule this to happen every 200ms). + +##ObservedDocShell +The only implementation of a MarkersStorage we have right now is an `ObservedDocShell`. + +Instances of `ObservedDocShell` accumulate markers that are *mostly* about a particular docshell. At a high level, for example, an `ObservedDocshell` would be created when a timeline tool is opened on a page. It is reasonable to assume that most operations which are interesting for that particular page happen on the main thread. However certain operations may happen outside of it, yet are interesting for its developers, for which markers can be created as well (e.g. web audio stuff, service workers etc.). It is also reasonable to assume that a docshell may sometimes not be easily accessible from certain parts of the platform code, but for which markers still need to be created. + +Therefore, the following scenarios arise: + +- a). creating a marker on the main thread about a particular docshell + +- b). creating a marker on the main thread without pinpointing to an affected docshell (unlikely, but allowed; in this case such a marker would have to be stored in all currently existing `ObservedDocShell` instances) + +- c). creating a marker off the main thread about a particular docshell (impossible; docshells can't be referenced outside the main thread, in which case some other type of identification mechanism needs to be put in place). + +- d). creating a marker off the main thread without pinpointing to a particular docshell (same path as c. here, such a marker would have to be stored in all currently existing `ObservedDocShell` instances). + +An observed docshell (in other words, "a docshell for which a timeline tool was opened") can thus receive both main thread and off the main thread markers. + +Cross-process markers are unnecessary at the moment, but tracked in bug 1200120. + +##TimelineConsumers +A `TimelineConsumer` is a singleton that facilitates access to `ObservedDocShell` instances. This is where a docshell can register/unregister itself as being observed via the `AddConsumer` and `RemoveConsumer` methods. + +All markers may only be stored via this singleton. Certain helper methods are available: + +* Main thread only +`AddMarkerForDocShell(nsDocShell*, const char*, MarkerTracingType)` +`AddMarkerForDocShell(nsDocShell*, const char*, const TimeStamp&, MarkerTracingType)` +`AddMarkerForDocShell(nsDocShell*, UniquePtr<AbstractTimelineMarker>&&)` + +* Any thread +`AddMarkerForAllObservedDocShells(const char*, MarkerTracingType)` +`AddMarkerForAllObservedDocShells(const char*, const TimeStamp&, MarkerTracingType)` +`AddMarkerForAllObservedDocShells(UniquePtr<AbstractTimelineMarker>&)` + +The "main thread only" methods deal with point a). described above. The "any thread" methods deal with points b). and d). + +##AbstractTimelineMarker + +All markers inherit from this abstract class, providing a simple thread-safe extendable blueprint. + +Markers are readonly after instantiation, and will always be identified by a name, a timestamp and their tracing type (`START`, `END`, `TIMESTAMP`). It *should not* make sense to modify their data after their creation. + +There are only two accessible constructors: +`AbstractTimelineMarker(const char*, MarkerTracingType)` +`AbstractTimelineMarker(const char*, const TimeStamp&, MarkerTracingType)` +which create a marker with a name and a tracing type. If unspecified, the corresponding timestamp will be the current instantiation time. Instantiating a marker *much later* after a particular operation is possible, but be careful providing the correct timestamp. + +The `AddDetails` virtual method should be implemented by subclasses when creating WebIDL versions of these markers, which will be sent over to a JavaScript frontend. + +##TimelineMarker +A `TimelineMarker` is the main `AbstractTimelineMarker` implementation. They allow attaching a JavaScript stack on `START` and `TIMESTAMP` markers. + +These markers will be created when using the `TimelineConsumers` helper methods which take in a string, a tracing type and (optionally) a timestamp. For more complex markers, subclasses are encouraged. See `EventTimelineMarker` or `ConsoleTimelineMarker` for some examples. + +##RAII + +### mozilla::AutoTimelineMarker + +The easiest way to trace Gecko events/tasks with start and end timeline markers is to use the `mozilla::AutoTimelineMarker` RAII class. It automatically adds the start marker on construction, and adds the end marker on destruction. Don't worry too much about potential performance impact! It only actually adds the markers when the given docshell is being observed by a timeline consumer, so essentially nothing will happen if a tool to inspect those markers isn't specifically open. + +This class may only be used on the main thread, and pointer to a docshell is necessary. If the docshell is a nullptr, nothing happens and this operation fails silently. + +Example: `AutoTimelineMarker marker(aTargetNode->OwnerDoc()->GetDocShell(), "Parse HTML");` + +### mozilla::AutoGlobalTimelineMarker` + +Similar to the previous RAII class, but doesn't expect a specific docshell, and the marker will be visible in all timeline consumers. This is useful for generic operations that don't involve a particular docshell, or where a docshell isn't accessible. May also only be used on the main thread. + +Example: `AutoGlobalTimelineMarker marker("Some global operation");` |