Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(267)

Unified Diff: tracing/tracing/metrics/system_health/loading_metric.html

Issue 2862103002: Fix main frame detection in loading metrics. (Closed)
Patch Set: rebase Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tracing/tracing/metrics/system_health/loading_metric.html
diff --git a/tracing/tracing/metrics/system_health/loading_metric.html b/tracing/tracing/metrics/system_health/loading_metric.html
index e9e5a4cbdd177917b81e3920efd6dda6fe4daec7..2adf697a5bd599cc815c1a227c39c88e526a8ba3 100644
--- a/tracing/tracing/metrics/system_health/loading_metric.html
+++ b/tracing/tracing/metrics/system_health/loading_metric.html
@@ -19,12 +19,156 @@ found in the LICENSE file.
'use strict';
tr.exportTo('tr.metrics.sh', function() {
+ const PLUGIN_URL = 'data:text/html,pluginplaceholderdata';
const RESPONSIVENESS_THRESHOLD_MS = 50;
const INTERACTIVE_WINDOW_SIZE_MS = 5 * 1000;
const timeDurationInMs_smallerIsBetter =
tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;
const RelatedEventSet = tr.v.d.RelatedEventSet;
+
+ /**
+ * Helper class for detecting whether a frame is the main frame.
+ *
+ * The class uses frame loader snapshots and special main frame markers in the
+ * trace to compute the isMainFrame predicate. A frame with the given frame id
+ * F is the main frame at the given time T if the following conditions hold:
+ * 1) There is a frame loader with the frame id F that is alive at time T.
+ * 2) There is an event (category='loading', title='markAsMainFrame') with the
+ * frame id F that occurs during the lifetime of the frame loader.
+ * 3) The frame is not a plug-in frame, i.e. the frame loader does not have a
+ * snapshot with the URL equal to the PLUGIN_URL.
+ * See https://bugs.chromium.org/p/chromium/issues/detail?id=692112#c10 for
+ * more info on main frame detection.
+ */
+ class MainFrameHelper {
+ /**
+ * @param {!tr.model.helpers.ChromeRendererHelper} rendererHelper
+ */
+ constructor(rendererHelper) {
+ this.frameLoaders_ = rendererHelper.process.objects
+ .getAllInstancesNamed('FrameLoader') || [];
+ this.mainFrameMarkers_ = this.findMainFrameMarkers_(rendererHelper);
+ this.mainFrameLiveRanges_ = this.findMainFrameLiveRanges_(rendererHelper);
+ }
+
+ /**
+ * Returns true if the frame with the given frameId is the main frame at
+ * given time.
+ * @param {string} frameId
+ * @param {number} timestamp
+ * @returns {boolean}
+ */
+ isMainFrame(frameId, timestamp) {
+ for (const {frameId: mainFrameId, liveRange} of
+ this.mainFrameLiveRanges_) {
+ if (mainFrameId === frameId &&
+ liveRange.containsExplicitRangeInclusive(timestamp, timestamp)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the document URL that this being loaded in the given frame at the
+ * given time.
+ * @param {string} frameId
+ * @param {number} timestamp
+ * @returns {string}
+ */
+ getURL(frameId, timestamp) {
+ for (const frameLoader of this.frameLoaders_) {
+ if (!frameLoader.isAliveAt(timestamp)) continue;
+ const snapshot = frameLoader.getSnapshotAt(timestamp);
+ if (frameId === snapshot.args.frame.id_ref) {
+ return snapshot.args.documentLoaderURL;
+ }
+ }
+ return undefined;
+ }
+
+ /**
+ * Returns a list of main frame markers.
+ * @private
+ * @param {!tr.model.helpers.ChromeRendererHelper} rendererHelper
+ * @returns {!Array.<{frameId: string, timestamp: number}>}
+ */
+ findMainFrameMarkers_(rendererHelper) {
+ return findAllEvents(rendererHelper, 'loading', 'markAsMainFrame').
+ map(event => {
+ return {frameId: event.args.frame, timestamp: event.start};
+ });
+ }
+
+ /**
+ * Returns a list of main frame ids and live ranges.
+ * @private
+ * @returns {!Array.<{frameId: string, liveRange: !tr.b.Range}>}
+ */
+ findMainFrameLiveRanges_() {
+ const result = [];
+ for (const frameLoader of this.frameLoaders_) {
+ if (frameLoader.snapshots.length > 0 &&
+ this.isMarkedAsMainFrame_(frameLoader) &&
+ !this.isPluginFrame_(frameLoader)) {
+ result.push({
+ frameId: this.getFrameId_(frameLoader),
+ liveRange: frameLoader.bounds
+ });
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Checks if the given frame loader has a main frame marker event.
+ * @private
+ * @param {!FrameLoader} frameLoader
+ * @returns {boolean}
+ */
+ isMarkedAsMainFrame_(frameLoader) {
+ const currentFrameId = this.getFrameId_(frameLoader);
+ frameLoader.updateBounds();
+ const liveRange = frameLoader.bounds;
+ for (const {frameId, timestamp} of this.mainFrameMarkers_) {
+ if (currentFrameId === frameId &&
+ liveRange.containsExplicitRangeInclusive(timestamp, timestamp)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the given frame loader loads the PLUGIN_URL.
+ * @private
+ * @param {!FrameLoader} frameLoader
+ * @returns {boolean}
+ */
+ isPluginFrame_(frameLoader) {
+ for (const snapshot of frameLoader.snapshots) {
+ if (snapshot.args.documentLoaderURL === PLUGIN_URL) return true;
+ }
+ return false;
+ }
+
+ /**
+ * @private
+ * @param {!FrameLoader} frameLoader
+ * @returns {number} the frame id corresponding to the given frame loader.
+ */
+ getFrameId_(frameLoader) {
+ const result = frameLoader.snapshots[0].args.frame.id_ref;
+ for (const snapshot of frameLoader.snapshots) {
+ if (snapshot.args.frame.id_ref !== result) {
+ throw new Error('Snapshots have different frame ids.');
+ }
+ }
+ return result;
+ }
+ }
+
/**
* @param {!tr.model.Process} process
* @param {!tr.b.math.Range} range
@@ -79,18 +223,18 @@ tr.exportTo('tr.metrics.sh', function() {
if (!hasCategoryAndName(ev, 'blink.user_timing', 'navigationStart')) {
continue;
}
- const frameIdRef = ev.args.frame;
- let list = this.navigationStartsForFrameId_[frameIdRef];
+ const frameId = ev.args.frame;
+ let list = this.navigationStartsForFrameId_[frameId];
if (list === undefined) {
- this.navigationStartsForFrameId_[frameIdRef] = list = [];
+ this.navigationStartsForFrameId_[frameId] = list = [];
}
list.unshift(ev);
}
}
NavigationStartFinder.prototype = {
- findNavigationStartEventForFrameBeforeTimestamp(frameIdRef, ts) {
- const list = this.navigationStartsForFrameId_[frameIdRef];
+ findNavigationStartEventForFrameBeforeTimestamp(frameId, ts) {
+ const list = this.navigationStartsForFrameId_[frameId];
if (list === undefined) return undefined;
let eventBeforeTimestamp;
for (const ev of list) {
@@ -123,22 +267,6 @@ tr.exportTo('tr.metrics.sh', function() {
return histogram;
}
- function findFrameLoaderSnapshotAt(rendererHelper, frameIdRef, ts) {
- const objects = rendererHelper.process.objects;
- const frameLoaderInstances = objects.instancesByTypeName_.FrameLoader;
- if (frameLoaderInstances === undefined) return undefined;
-
- let snapshot;
- for (const instance of frameLoaderInstances) {
- if (!instance.isAliveAt(ts)) continue;
- const maybeSnapshot = instance.getSnapshotAt(ts);
- if (frameIdRef !== maybeSnapshot.args.frame.id_ref) continue;
- snapshot = maybeSnapshot;
- }
-
- return snapshot;
- }
-
function findAllEvents(rendererHelper, category, title) {
const targetEvents = [];
@@ -157,21 +285,21 @@ tr.exportTo('tr.metrics.sh', function() {
continue;
}
if (rendererHelper.isTelemetryInternalEvent(ev)) continue;
- const frameIdRef = ev.args.frame;
- if (frameIdRef === undefined) continue;
- let list = candidatesForFrameId[frameIdRef];
+ const frameId = ev.args.frame;
+ if (frameId === undefined) continue;
+ let list = candidatesForFrameId[frameId];
if (list === undefined) {
- candidatesForFrameId[frameIdRef] = list = [];
+ candidatesForFrameId[frameId] = list = [];
}
list.push(ev);
}
return candidatesForFrameId;
}
+ // TODO(ulan): Remove URL blacklist.
const URL_BLACKLIST = [
'about:blank',
// Chrome on Android creates main frames with the below URL for plugins.
- 'data:text/html,pluginplaceholderdata',
// Special URL used to start a navigation to an unreachable error page.
'data:text/html,chromewebdata'
];
@@ -180,20 +308,18 @@ tr.exportTo('tr.metrics.sh', function() {
}
function collectTimeToEvent(
- category, eventName, rendererHelper, navigationStartFinder) {
+ category, eventName, rendererHelper,
+ navigationStartFinder, mainFrameHelper) {
const targetEvents = findAllEvents(rendererHelper, category, eventName);
const samples = [];
for (const ev of targetEvents) {
if (rendererHelper.isTelemetryInternalEvent(ev)) continue;
- const frameIdRef = ev.args.frame;
- const snapshot =
- findFrameLoaderSnapshotAt(rendererHelper, frameIdRef, ev.start);
- if (snapshot === undefined || !snapshot.args.isLoadingMainFrame) continue;
- const url = snapshot.args.documentLoaderURL;
- if (shouldIgnoreURL(url)) continue;
+ const frameId = ev.args.frame;
+ if (!mainFrameHelper.isMainFrame(frameId, ev.start)) continue;
+ const url = mainFrameHelper.getURL(frameId, ev.start);
+ if (url === undefined || shouldIgnoreURL(url)) continue;
const navigationStartEvent = navigationStartFinder.
- findNavigationStartEventForFrameBeforeTimestamp(
- frameIdRef, ev.start);
+ findNavigationStartEventForFrameBeforeTimestamp(frameId, ev.start);
// Ignore layout w/o preceding navigationStart, as they are not
// attributed to any time-to-X metric.
if (navigationStartEvent === undefined) continue;
@@ -208,13 +334,7 @@ tr.exportTo('tr.metrics.sh', function() {
}
function addFirstMeaningfulPaintSample(samples, rendererHelper,
- frameIdRef, navigationStart, fmpMarkerEvent) {
- const snapshot = findFrameLoaderSnapshotAt(
- rendererHelper, frameIdRef, fmpMarkerEvent.start);
- if (!snapshot || !snapshot.args.isLoadingMainFrame) return;
- const url = snapshot.args.documentLoaderURL;
- if (shouldIgnoreURL(url)) return;
-
+ navigationStart, fmpMarkerEvent, url) {
const navStartToFMPRange = tr.b.math.Range.fromExplicitRange(
navigationStart.start, fmpMarkerEvent.start);
const networkEvents = getNetworkEventsInRange(
@@ -241,15 +361,9 @@ tr.exportTo('tr.metrics.sh', function() {
}
function addFirstMeaningfulPaintCpuTimeSample(samples, rendererHelper,
- frameIdRef, navigationStart, fmpMarkerEvent) {
+ navigationStart, fmpMarkerEvent, url) {
const navStartToFMPCpuRange = tr.b.math.Range.fromExplicitRange(
navigationStart.cpuStart, fmpMarkerEvent.cpuStart);
- const snapshot = findFrameLoaderSnapshotAt(
- rendererHelper, frameIdRef, fmpMarkerEvent.start);
- if (!snapshot || !snapshot.args.isLoadingMainFrame) return;
- const url = snapshot.args.documentLoaderURL;
- if (shouldIgnoreURL(url)) return;
-
const mainThreadCpuTime = getMainThreadCpuTime(
rendererHelper, navStartToFMPCpuRange);
@@ -287,7 +401,6 @@ tr.exportTo('tr.metrics.sh', function() {
function addFirstInteractiveSample(samples, rendererHelper,
navigationStart, firstMeaningfulPaint, url) {
- if (shouldIgnoreURL(url)) return;
const navigationStartTime = navigationStart.start;
let firstInteractive = Infinity;
let firstInteractiveCandidate = firstMeaningfulPaint;
@@ -364,18 +477,21 @@ tr.exportTo('tr.metrics.sh', function() {
* Design doc: https://goo.gl/ISWndc
*/
function collectFirstMeaningfulPaintAndTimeToInteractiveForRenderer(
- rendererHelper, navigationStartFinder) {
+ rendererHelper, navigationStartFinder, mainFrameHelper) {
const firstMeaningfulPaintSamples = [];
const firstMeaningfulPaintCpuTimeSamples = [];
const firstInteractiveSamples = [];
- function addSamples(frameIdRef, navigationStart, fmpMarkerEvent) {
+ function addSamples(frameId, navigationStart, fmpMarkerEvent) {
+ if (!mainFrameHelper.isMainFrame(frameId, fmpMarkerEvent.start)) return;
+ const url = mainFrameHelper.getURL(frameId, fmpMarkerEvent.start);
+ if (url === undefined || shouldIgnoreURL(url)) return;
const data = addFirstMeaningfulPaintSample(
firstMeaningfulPaintSamples, rendererHelper,
- frameIdRef, navigationStart, fmpMarkerEvent);
+ navigationStart, fmpMarkerEvent, url);
addFirstMeaningfulPaintCpuTimeSample(
firstMeaningfulPaintCpuTimeSamples, rendererHelper,
- frameIdRef, navigationStart, fmpMarkerEvent);
+ navigationStart, fmpMarkerEvent, url);
if (data !== undefined) {
addFirstInteractiveSample(
firstInteractiveSamples, rendererHelper,
@@ -386,15 +502,15 @@ tr.exportTo('tr.metrics.sh', function() {
const candidatesForFrameId =
findFirstMeaningfulPaintCandidates(rendererHelper);
- for (const frameIdRef in candidatesForFrameId) {
+ for (const frameId in candidatesForFrameId) {
let navigationStart = undefined;
let lastCandidate = undefined;
// Iterate over the FMP candidates, remembering the last one.
- for (const ev of candidatesForFrameId[frameIdRef]) {
+ for (const ev of candidatesForFrameId[frameId]) {
const navigationStartForThisCandidate = navigationStartFinder.
findNavigationStartEventForFrameBeforeTimestamp(
- frameIdRef, ev.start);
+ frameId, ev.start);
// Ignore candidate w/o preceding navigationStart, as they are not
// attributed to any TTFMP.
if (navigationStartForThisCandidate === undefined) continue;
@@ -403,7 +519,7 @@ tr.exportTo('tr.metrics.sh', function() {
// New navigation is found. Compute TTFMP for current navigation,
// and reset the state variables.
if (navigationStart !== undefined && lastCandidate !== undefined) {
- addSamples(frameIdRef, navigationStart, lastCandidate);
+ addSamples(frameId, navigationStart, lastCandidate);
}
navigationStart = navigationStartForThisCandidate;
}
@@ -412,7 +528,7 @@ tr.exportTo('tr.metrics.sh', function() {
// Compute TTFMP for the last navigation.
if (lastCandidate !== undefined) {
- addSamples(frameIdRef, navigationStart, lastCandidate);
+ addSamples(frameId, navigationStart, lastCandidate);
}
}
return {
@@ -424,17 +540,18 @@ tr.exportTo('tr.metrics.sh', function() {
function collectLoadingMetricsForRenderer(rendererHelper) {
const navigationStartFinder = new NavigationStartFinder(rendererHelper);
+ const mainFrameHelper = new MainFrameHelper(rendererHelper);
const firstContentfulPaintSamples = collectTimeToEvent(
'loading', 'firstContentfulPaint',
- rendererHelper, navigationStartFinder);
+ rendererHelper, navigationStartFinder, mainFrameHelper);
const onLoadSamples = collectTimeToEvent(
'blink.user_timing', 'loadEventStart',
- rendererHelper, navigationStartFinder);
+ rendererHelper, navigationStartFinder, mainFrameHelper);
const {firstMeaningfulPaintSamples, firstMeaningfulPaintCpuTimeSamples,
firstInteractiveSamples} =
collectFirstMeaningfulPaintAndTimeToInteractiveForRenderer(
- rendererHelper, navigationStartFinder);
+ rendererHelper, navigationStartFinder, mainFrameHelper);
return {
firstContentfulPaintSamples,
onLoadSamples,
@@ -506,6 +623,7 @@ tr.exportTo('tr.metrics.sh', function() {
collectLoadingMetricsForRenderer,
RESPONSIVENESS_THRESHOLD_MS,
INTERACTIVE_WINDOW_SIZE_MS,
+ MainFrameHelper, // For testing.
};
});
</script>

Powered by Google App Engine
This is Rietveld 408576698