Bug 1177226 - Support User Timing API events in the Developer HUD. r=ehsan, r=jryans

This commit is contained in:
Russ Nicoletti 2015-07-31 15:40:08 -07:00
parent bb0be845a6
commit 6ec6ba48ee
12 changed files with 249 additions and 3 deletions

View File

@ -1167,3 +1167,7 @@ pref("dom.audiochannel.mutedByDefault", true);
// Default device name for Presentation API
pref("dom.presentation.device.name", "Firefox OS");
// Enable notification of performance timing
pref("dom.performance.enable_notify_performance_timing", true);

View File

@ -25,6 +25,10 @@ XPCOMUtils.defineLazyGetter(this, 'EventLoopLagFront', function() {
return devtools.require('devtools/server/actors/eventlooplag').EventLoopLagFront;
});
XPCOMUtils.defineLazyGetter(this, 'PerformanceEntriesFront', function() {
return devtools.require('devtools/server/actors/performance-entries').PerformanceEntriesFront;
});
XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() {
return devtools.require('devtools/server/actors/memory').MemoryFront;
});
@ -588,6 +592,76 @@ let eventLoopLagWatcher = {
};
developerHUD.registerWatcher(eventLoopLagWatcher);
/*
* The performanceEntriesWatcher determines the delta between the epoch
* of an app's launch time and the app's performance entry marks.
* When it receives an "appLaunch" performance entry mark it records the
* name of the app being launched and the epoch of when the launch ocurred.
* When it receives subsequent performance entry events for the app being
* launched, it records the delta of the performance entry opoch compared
* to the app-launch epoch and emits an "app-start-time-<performance mark name>"
* event containing the delta.
*/
let performanceEntriesWatcher = {
_client: null,
_fronts: new Map(),
_appLaunchName: null,
_appLaunchStartTime: null,
init(client) {
this._client = client;
},
trackTarget(target) {
// The performanceEntries watcher doesn't register a metric because
// currently the metrics generated are not displayed in
// in the front-end.
let front = new PerformanceEntriesFront(this._client, target.actor);
this._fronts.set(target, front);
// User timings are always gathered; there is no setting to enable/
// disable.
front.start();
front.on('entry', detail => {
if (detail.type === 'mark') {
let name = detail.name;
let epoch = detail.epoch;
let CHARS_UNTIL_APP_NAME = 7; // '@app://'
// FIXME There is a potential race condition that can result
// in some performance entries being disregarded. See bug 1189942.
if (name.indexOf('appLaunch') != -1) {
let appStartPos = name.indexOf('@app') + CHARS_UNTIL_APP_NAME;
let length = (name.indexOf('.') - appStartPos);
this._appLaunchName = name.substr(appStartPos, length);
this._appLaunchStartTime = epoch;
} else {
let origin = detail.origin;
origin = origin.substr(0, origin.indexOf('.'));
if (this._appLaunchName === origin) {
let time = epoch - this._appLaunchStartTime;
let eventName = 'app-startup-time-' + name;
// Events based on performance marks are for telemetry only, they are
// not displayed in the HUD front end.
target._sendTelemetryEvent({name: eventName, value: time});
}
}
}
});
},
untrackTarget(target) {
let fronts = this._fronts;
if (fronts.has(target)) {
fronts.get(target).destroy();
fronts.delete(target);
}
}
};
developerHUD.registerWatcher(performanceEntriesWatcher);
/**
* The Memory Watcher uses devtools actors to track memory usage.

View File

@ -262,6 +262,7 @@ bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false;
bool nsContentUtils::sEncodeDecodeURLHash = false;
bool nsContentUtils::sGettersDecodeURLHash = false;
bool nsContentUtils::sPrivacyResistFingerprinting = false;
bool nsContentUtils::sSendPerformanceTimingNotifications = false;
uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
@ -553,6 +554,9 @@ nsContentUtils::Init()
"dom.event.handling-user-input-time-limit",
1000);
Preferences::AddBoolVarCache(&sSendPerformanceTimingNotifications,
"dom.performance.enable_notify_performance_timing", false);
#if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
Preferences::AddBoolVarCache(&sDOMWindowDumpEnabled,
"browser.dom.window.dump.enabled");

View File

@ -1948,6 +1948,14 @@ public:
return sIsResourceTimingEnabled;
}
/*
* Returns true if notification should be sent for peformance timing events.
*/
static bool SendPerformanceTimingNotifications()
{
return sSendPerformanceTimingNotifications;
}
/*
* Returns true if URL setters should percent encode the Hash/Ref segment
* and getters should return the percent decoded value of the segment
@ -2544,6 +2552,7 @@ private:
static bool sEncodeDecodeURLHash;
static bool sGettersDecodeURLHash;
static bool sPrivacyResistFingerprinting;
static bool sSendPerformanceTimingNotifications;
static nsHtml5StringParser* sHTMLFragmentParser;
static nsIParser* sXMLFragmentParser;

View File

@ -21,6 +21,7 @@
#include "PerformanceResourceTiming.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/PerformanceBinding.h"
#include "mozilla/dom/PerformanceEntryEvent.h"
#include "mozilla/dom/PerformanceTimingBinding.h"
#include "mozilla/dom/PerformanceNavigationBinding.h"
#include "mozilla/Preferences.h"
@ -738,14 +739,24 @@ nsPerformance::InsertUserEntry(PerformanceEntry* aEntry)
{
MOZ_ASSERT(NS_IsMainThread());
if (nsContentUtils::IsUserTimingLoggingEnabled()) {
nsAutoCString uri;
nsAutoCString uri;
uint64_t markCreationEpoch = 0;
if (nsContentUtils::IsUserTimingLoggingEnabled() ||
nsContentUtils::SendPerformanceTimingNotifications()) {
nsresult rv = GetOwner()->GetDocumentURI()->GetHost(uri);
if(NS_FAILED(rv)) {
// If we have no URI, just put in "none".
uri.AssignLiteral("none");
}
PerformanceBase::LogEntry(aEntry, uri);
markCreationEpoch = static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC);
if (nsContentUtils::IsUserTimingLoggingEnabled()) {
PerformanceBase::LogEntry(aEntry, uri);
}
}
if (nsContentUtils::SendPerformanceTimingNotifications()) {
TimingNotification(aEntry, uri, markCreationEpoch);
}
PerformanceBase::InsertUserEntry(aEntry);
@ -986,6 +997,29 @@ PerformanceBase::LogEntry(PerformanceEntry* aEntry, const nsACString& aOwner) co
static_cast<uint64_t>(PR_Now() / PR_USEC_PER_MSEC));
}
void
PerformanceBase::TimingNotification(PerformanceEntry* aEntry, const nsACString& aOwner, uint64_t aEpoch)
{
PerformanceEntryEventInit init;
init.mBubbles = false;
init.mCancelable = false;
init.mName = aEntry->GetName();
init.mEntryType = aEntry->GetEntryType();
init.mStartTime = aEntry->StartTime();
init.mDuration = aEntry->Duration();
init.mEpoch = aEpoch;
init.mOrigin = NS_ConvertUTF8toUTF16(aOwner.BeginReading());
nsRefPtr<PerformanceEntryEvent> perfEntryEvent =
PerformanceEntryEvent::Constructor(this, NS_LITERAL_STRING("performanceentry"), init);
nsCOMPtr<EventTarget> et = do_QueryInterface(GetOwner());
if (et) {
bool dummy = false;
et->DispatchEvent(perfEntryEvent, &dummy);
}
}
void
PerformanceBase::InsertUserEntry(PerformanceEntry* aEntry)
{

View File

@ -351,6 +351,7 @@ protected:
}
void LogEntry(PerformanceEntry* aEntry, const nsACString& aOwner) const;
void TimingNotification(PerformanceEntry* aEntry, const nsACString& aOwner, uint64_t epoch);
private:
nsTArray<nsRefPtr<PerformanceEntry>> mUserEntries;

View File

@ -0,0 +1,27 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*/
dictionary PerformanceEntryEventInit : EventInit
{
DOMString name = "";
DOMString entryType = "";
DOMHighResTimeStamp startTime = 0;
DOMHighResTimeStamp duration = 0;
double epoch = 0;
DOMString origin = "";
};
[Constructor(DOMString type, optional PerformanceEntryEventInit eventInitDict),
ChromeOnly]
interface PerformanceEntryEvent : Event
{
readonly attribute DOMString name;
readonly attribute DOMString entryType;
readonly attribute DOMHighResTimeStamp startTime;
readonly attribute DOMHighResTimeStamp duration;
readonly attribute double epoch;
readonly attribute DOMString origin;
};

View File

@ -779,6 +779,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [
'MozStkCommandEvent.webidl',
'MozVoicemailEvent.webidl',
'PageTransitionEvent.webidl',
'PerformanceEntryEvent.webidl',
'PluginCrashedEvent.webidl',
'PopStateEvent.webidl',
'PopupBlockedEvent.webidl',

View File

@ -160,6 +160,9 @@ pref("dom.enable_user_timing", true);
// Enable printing performance marks/measures to log
pref("dom.performance.enable_user_timing_logging", false);
// Enable notification of performance timing
pref("dom.performance.enable_notify_performance_timing", false);
// Whether the Gamepad API is enabled
pref("dom.gamepad.enabled", true);
#ifdef RELEASE_BUILD

View File

@ -0,0 +1,83 @@
/* 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/. */
/**
* The performanceEntries actor emits events corresponding to performance
* entries. It receives `performanceentry` events containing the performance
* entry details and emits an event containing the name, type, origin, and
* epoch of the performance entry.
*/
const {
method, Arg, Option, RetVal, Front, FrontClass, Actor, ActorClass
} = require("devtools/server/protocol");
const events = require("sdk/event/core");
let PerformanceEntriesActor = exports.PerformanceEntriesActor = ActorClass({
typeName: "performanceEntries",
listenerAdded: false,
events: {
"entry" : {
type: "entry",
detail: Arg(0, "json") // object containing performance entry name, type,
// origin, and epoch.
}
},
initialize: function(conn, tabActor) {
Actor.prototype.initialize.call(this, conn);
this.window = tabActor.window;
},
/**
* Start tracking the user timings.
*/
start: method(function() {
if (!this.listenerAdded) {
this.onPerformanceEntry = this.onPerformanceEntry.bind(this);
this.window.addEventListener("performanceentry", this.onPerformanceEntry, true);
this.listenerAdded = true;
}
}),
/**
* Stop tracking the user timings.
*/
stop: method(function() {
if (this.listenerAdded) {
this.window.removeEventListener("performanceentry", this.onPerformanceEntry, true);
this.listenerAdded = false;
}
}),
disconnect: function() {
this.destroy();
},
destroy: function() {
this.stop();
Actor.prototype.destroy.call(this);
},
onPerformanceEntry: function (e) {
let emitDetail = {
type: e.entryType,
name: e.name,
origin: e.origin,
epoch: e.epoch
};
events.emit(this, 'entry', emitDetail);
}
});
exports.PerformanceEntriesFront = FrontClass(PerformanceEntriesActor, {
initialize: function(client, form) {
Front.prototype.initialize.call(this, client);
this.actorID = form.performanceEntriesActor;
this.manage(this);
},
});

View File

@ -545,6 +545,11 @@ var DebuggerServer = {
constructor: "PromisesActor",
type: { global: true, tab: true }
});
this.registerModule("devtools/server/actors/performance-entries", {
prefix: "performanceEntries",
constructor: "PerformanceEntriesActor",
type: { tab: true }
});
},
/**

View File

@ -75,6 +75,7 @@ EXTRA_JS_MODULES.devtools.server.actors += [
'actors/memory.js',
'actors/monitor.js',
'actors/object.js',
'actors/performance-entries.js',
'actors/preference.js',
'actors/pretty-print-worker.js',
'actors/profiler.js',