Bug 1050500 - Add callee information to Javascript timeline markers. r=smaug

This commit is contained in:
Tom Tromey 2015-05-20 05:28:00 -04:00
parent fe2b6d5e46
commit f0669fbdd0
11 changed files with 175 additions and 32 deletions

View File

@ -22,7 +22,8 @@ TimelineMarker::TimelineMarker(nsDocShell* aDocShell, const char* aName,
TimelineMarker::TimelineMarker(nsDocShell* aDocShell, const char* aName,
TracingMetadata aMetaData,
const nsAString& aCause)
const nsAString& aCause,
TimelineStackRequest aStackRequest)
: mName(aName)
, mMetaData(aMetaData)
, mCause(aCause)
@ -30,7 +31,7 @@ TimelineMarker::TimelineMarker(nsDocShell* aDocShell, const char* aName,
MOZ_COUNT_CTOR(TimelineMarker);
MOZ_ASSERT(aName);
aDocShell->Now(&mTime);
if (aMetaData == TRACING_INTERVAL_START) {
if (aMetaData == TRACING_INTERVAL_START && aStackRequest != NO_STACK) {
CaptureStack();
}
}

View File

@ -21,12 +21,15 @@ class nsDocShell;
class TimelineMarker
{
public:
enum TimelineStackRequest { STACK, NO_STACK };
TimelineMarker(nsDocShell* aDocShell, const char* aName,
TracingMetadata aMetaData);
TimelineMarker(nsDocShell* aDocShell, const char* aName,
TracingMetadata aMetaData,
const nsAString& aCause);
const nsAString& aCause,
TimelineStackRequest aStackRequest = STACK);
virtual ~TimelineMarker();
@ -43,7 +46,9 @@ public:
// called on both the starting and ending markers of a pair.
// Ordinarily the ending marker doesn't need to do anything
// here.
virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) {}
virtual void AddDetails(JSContext* aCx,
mozilla::dom::ProfileTimelineMarker& aMarker)
{}
virtual void AddLayerRectangles(
mozilla::dom::Sequence<mozilla::dom::ProfileTimelineLayerRect>&)

View File

@ -2997,7 +2997,7 @@ nsDocShell::PopProfileTimelineMarkers(
marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
marker->mStart = startPayload->GetTime();
marker->mEnd = startPayload->GetTime();
startPayload->AddDetails(*marker);
startPayload->AddDetails(aCx, *marker);
continue;
}
@ -3045,8 +3045,8 @@ nsDocShell::PopProfileTimelineMarkers(
if (isPaint) {
marker->mRectangles.Construct(layerRectangles);
}
startPayload->AddDetails(*marker);
endPayload->AddDetails(*marker);
startPayload->AddDetails(aCx, *marker);
endPayload->AddDetails(aCx, *marker);
}
// We want the start to be dropped either way.
@ -13912,27 +13912,61 @@ class JavascriptTimelineMarker : public TimelineMarker
{
public:
JavascriptTimelineMarker(nsDocShell* aDocShell, const char* aName,
const char* aReason)
const char* aReason,
const char16_t* aFunctionName,
const char16_t* aFileName,
uint32_t aLineNumber)
: TimelineMarker(aDocShell, aName, TRACING_INTERVAL_START,
NS_ConvertUTF8toUTF16(aReason))
NS_ConvertUTF8toUTF16(aReason),
NO_STACK)
, mFunctionName(aFunctionName)
, mFileName(aFileName)
, mLineNumber(aLineNumber)
{
}
void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) override
void AddDetails(JSContext* aCx, mozilla::dom::ProfileTimelineMarker& aMarker)
override
{
aMarker.mCauseName.Construct(GetCause());
if (!mFunctionName.IsEmpty() || !mFileName.IsEmpty()) {
ProfileTimelineStackFrame stackFrame;
stackFrame.mLine.Construct(mLineNumber);
stackFrame.mSource.Construct(mFileName);
stackFrame.mFunctionDisplayName.Construct(mFunctionName);
JS::Rooted<JS::Value> newStack(aCx);
if (ToJSValue(aCx, stackFrame, &newStack)) {
if (newStack.isObject()) {
aMarker.mStack = &newStack.toObject();
}
} else {
JS_ClearPendingException(aCx);
}
}
}
private:
nsString mFunctionName;
nsString mFileName;
uint32_t mLineNumber;
};
void
nsDocShell::NotifyJSRunToCompletionStart(const char* aReason)
nsDocShell::NotifyJSRunToCompletionStart(const char* aReason,
const char16_t* aFunctionName,
const char16_t* aFilename,
const uint32_t aLineNumber)
{
bool timelineOn = nsIDocShell::GetRecordProfileTimelineMarkers();
// If first start, mark interval start.
if (timelineOn && mJSRunToCompletionDepth == 0) {
mozilla::UniquePtr<TimelineMarker> marker =
MakeUnique<JavascriptTimelineMarker>(this, "Javascript", aReason);
MakeUnique<JavascriptTimelineMarker>(this, "Javascript", aReason,
aFunctionName, aFilename,
aLineNumber);
AddProfileTimelineMarker(Move(marker));
}
mJSRunToCompletionDepth++;

View File

@ -54,7 +54,7 @@ interface nsITabParent;
typedef unsigned long nsLoadFlags;
[scriptable, builtinclass, uuid(bf78de98-9e88-498d-bc19-0e138f683939)]
[scriptable, builtinclass, uuid(696b32a1-3cf1-4909-b501-474b25fc7954)]
interface nsIDocShell : nsIDocShellTreeItem
{
/**
@ -1039,7 +1039,10 @@ interface nsIDocShell : nsIDocShellTreeItem
* that execution has stopped. This only occurs when the Timeline devtool
* is collecting information.
*/
[noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStart(in string aReason);
[noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStart(in string aReason,
in wstring functionName,
in wstring fileName,
in unsigned long lineNumber);
[noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStop();
/**

View File

@ -26,6 +26,8 @@ let TESTS = [{
// cause.
let jsMarkers = markers.filter(m => m.name == "Javascript" && m.causeName);
ok(jsMarkers.length > 0, "Got some Javascript markers");
is(jsMarkers[0].stack.functionDisplayName, "do_xhr",
"Javascript marker has entry point name");
}
}];

View File

@ -1019,7 +1019,8 @@ public:
return GetCause() == aOther.GetCause();
}
virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) override
virtual void AddDetails(JSContext* aCx,
mozilla::dom::ProfileTimelineMarker& aMarker) override
{
if (GetMetaData() == TRACING_INTERVAL_START) {
aMarker.mCauseName.Construct(GetCause());
@ -1041,7 +1042,8 @@ public:
MOZ_ASSERT(aMetaData == TRACING_TIMESTAMP);
}
virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) override
virtual void AddDetails(JSContext* aCx,
mozilla::dom::ProfileTimelineMarker& aMarker) override
{
if (!GetCause().IsEmpty()) {
aMarker.mCauseName.Construct(GetCause());

View File

@ -529,7 +529,6 @@ AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
aCx ? aCx : FindJSContext(aGlobalObject))
, ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ true)
, mWebIDLCallerPrincipal(nullptr)
, mDocShellForJSRunToCompletion(nullptr)
, mIsMainThread(aIsMainThread)
{
MOZ_ASSERT(aGlobalObject);
@ -540,23 +539,12 @@ AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
}
if (aIsMainThread && gRunToCompletionListeners > 0) {
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobalObject);
if (window) {
mDocShellForJSRunToCompletion = window->GetDocShell();
}
}
if (mDocShellForJSRunToCompletion) {
mDocShellForJSRunToCompletion->NotifyJSRunToCompletionStart(aReason);
mDocShellEntryMonitor.emplace(cx(), aReason);
}
}
AutoEntryScript::~AutoEntryScript()
{
if (mDocShellForJSRunToCompletion) {
mDocShellForJSRunToCompletion->NotifyJSRunToCompletionStop();
}
if (mIsMainThread) {
nsContentUtils::LeaveMicroTask();
}
@ -567,6 +555,76 @@ AutoEntryScript::~AutoEntryScript()
JS_MaybeGC(cx());
}
AutoEntryScript::DocshellEntryMonitor::DocshellEntryMonitor(JSContext* aCx,
const char* aReason)
: JS::dbg::AutoEntryMonitor(aCx)
, mReason(aReason)
{
}
void
AutoEntryScript::DocshellEntryMonitor::Entry(JSContext* aCx, JSFunction* aFunction,
JSScript* aScript)
{
JS::Rooted<JSFunction*> rootedFunction(aCx);
if (aFunction) {
rootedFunction = aFunction;
}
JS::Rooted<JSScript*> rootedScript(aCx);
if (aScript) {
rootedScript = aScript;
}
nsCOMPtr<nsPIDOMWindow> window =
do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)));
if (!window || !window->GetDocShell() ||
!window->GetDocShell()->GetRecordProfileTimelineMarkers()) {
return;
}
nsCOMPtr<nsIDocShell> docShellForJSRunToCompletion = window->GetDocShell();
nsString filename;
uint32_t lineNumber = 0;
js::AutoStableStringChars functionName(aCx);
if (rootedFunction) {
JS::Rooted<JSString*> displayId(aCx, JS_GetFunctionDisplayId(rootedFunction));
if (displayId) {
functionName.initTwoByte(aCx, displayId);
}
}
if (!rootedScript) {
rootedScript = JS_GetFunctionScript(aCx, rootedFunction);
}
if (rootedScript) {
filename = NS_ConvertUTF8toUTF16(JS_GetScriptFilename(rootedScript));
lineNumber = JS_GetScriptBaseLineNumber(aCx, rootedScript);
}
if (!filename.IsEmpty() || functionName.isTwoByte()) {
const char16_t* functionNameChars = functionName.isTwoByte() ?
functionName.twoByteChars() : nullptr;
docShellForJSRunToCompletion->NotifyJSRunToCompletionStart(mReason,
functionNameChars,
filename.BeginReading(),
lineNumber);
}
}
void
AutoEntryScript::DocshellEntryMonitor::Exit(JSContext* aCx)
{
nsCOMPtr<nsPIDOMWindow> window =
do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)));
// Not really worth checking GetRecordProfileTimelineMarkers here.
if (window && window->GetDocShell()) {
nsCOMPtr<nsIDocShell> docShellForJSRunToCompletion = window->GetDocShell();
docShellForJSRunToCompletion->NotifyJSRunToCompletionStop();
}
}
AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject)
: ScriptSettingsStackEntry(aGlobalObject, /* aCandidate = */ false)
, mCallerOverride(nsContentUtils::GetCurrentJSContextForThread())

View File

@ -16,6 +16,7 @@
#include "mozilla/Maybe.h"
#include "jsapi.h"
#include "js/Debug.h"
class nsPIDOMWindow;
class nsGlobalWindow;
@ -339,6 +340,30 @@ public:
}
private:
// A subclass of AutoEntryMonitor that notifies the docshell.
class DocshellEntryMonitor : public JS::dbg::AutoEntryMonitor
{
public:
DocshellEntryMonitor(JSContext* aCx, const char* aReason);
void Entry(JSContext* aCx, JSFunction* aFunction) override
{
Entry(aCx, aFunction, nullptr);
}
void Entry(JSContext* aCx, JSScript* aScript) override
{
Entry(aCx, nullptr, aScript);
}
void Exit(JSContext* aCx) override;
private:
void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript);
const char* mReason;
};
// It's safe to make this a weak pointer, since it's the subject principal
// when we go on the stack, so can't go away until after we're gone. In
// particular, this is only used from the CallSetup constructor, and only in
@ -349,7 +374,7 @@ private:
nsIPrincipal* MOZ_NON_OWNING_REF mWebIDLCallerPrincipal;
friend nsIPrincipal* GetWebIDLCallerPrincipal();
nsCOMPtr<nsIDocShell> mDocShellForJSRunToCompletion;
Maybe<DocshellEntryMonitor> mDocShellEntryMonitor;
bool mIsMainThread;
};

View File

@ -1037,7 +1037,8 @@ public:
{
}
virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) override
virtual void AddDetails(JSContext* aCx,
mozilla::dom::ProfileTimelineMarker& aMarker) override
{
if (GetMetaData() == TRACING_INTERVAL_START) {
aMarker.mType.Construct(GetCause());

View File

@ -4,6 +4,17 @@
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// For Javascript markers, the |stack| of a ProfileTimelineMarker
// holds an object of this type. It intentionally looks like a
// SavedStack object and is a representation of the frame that is
// about to be constructed at the entry point.
dictionary ProfileTimelineStackFrame {
long line;
long column = 0;
DOMString source;
DOMString functionDisplayName;
};
dictionary ProfileTimelineLayerRect {
long x = 0;
long y = 0;

View File

@ -109,7 +109,8 @@ public:
}
}
virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker) override
virtual void AddDetails(JSContext* aCx,
mozilla::dom::ProfileTimelineMarker& aMarker) override
{
if (GetMetaData() == TRACING_INTERVAL_START) {
aMarker.mRestyleHint.Construct(mRestyleHint);