 Chromium Code Reviews
 Chromium Code Reviews Issue 1963583005:
  Rewrite firstPaintMetric to support extracting multiple navigations from a single trace  (Closed) 
  Base URL: https://chromium.googlesource.com/external/github.com/catapult-project/catapult.git@master
    
  
    Issue 1963583005:
  Rewrite firstPaintMetric to support extracting multiple navigations from a single trace  (Closed) 
  Base URL: https://chromium.googlesource.com/external/github.com/catapult-project/catapult.git@master| Index: tracing/tracing/metrics/system_health/first_paint_metric.html | 
| diff --git a/tracing/tracing/metrics/system_health/first_paint_metric.html b/tracing/tracing/metrics/system_health/first_paint_metric.html | 
| index bbbcc22f14ecd72c49ebc7e8457a08a98cbea1af..a39b843abc46dc1d534e4e8c84b71e804cfb0646 100644 | 
| --- a/tracing/tracing/metrics/system_health/first_paint_metric.html | 
| +++ b/tracing/tracing/metrics/system_health/first_paint_metric.html | 
| @@ -19,10 +19,7 @@ tr.exportTo('tr.metrics.sh', function() { | 
| var timeDurationInMs_smallerIsBetter = | 
| tr.v.Unit.byName.timeDurationInMs_smallerIsBetter; | 
| - function findTargetRendererHelper(model) { | 
| - var chromeHelper = model.getOrCreateHelper( | 
| - tr.model.helpers.ChromeModelHelper); | 
| - | 
| + function findTargetRendererHelper(chromeHelper) { | 
| var largestPid = -1; | 
| for (var pid in chromeHelper.rendererHelpers) { | 
| var rendererHelper = chromeHelper.rendererHelpers[pid]; | 
| @@ -38,63 +35,122 @@ tr.exportTo('tr.metrics.sh', function() { | 
| return chromeHelper.rendererHelpers[largestPid]; | 
| } | 
| - function findNavigationStartEvent(rendererHelper) { | 
| - var navigationStartEvent = undefined; | 
| - | 
| + function navigationStartFinder(rendererHelper) { | 
| + var navigationStartsForFrameId = {}; | 
| rendererHelper.mainThread.sliceGroup.iterateAllEventsInThisContainer( | 
| () => true, function(ev) { | 
| - if (navigationStartEvent !== undefined || | 
| - ev.category !== 'blink.user_timing') | 
| + if (ev.category !== 'blink.user_timing' || | 
| + ev.title !== 'navigationStart') | 
| return; | 
| - if (ev.title === 'navigationStart') | 
| - navigationStartEvent = ev; | 
| + | 
| + var frameIdRef = ev.args['frame']; | 
| + var list = navigationStartsForFrameId[frameIdRef]; | 
| + if (list === undefined) { | 
| + navigationStartsForFrameId[frameIdRef] = list = []; | 
| + } | 
| + list.unshift(ev); | 
| }, | 
| this); | 
| - return navigationStartEvent; | 
| + return function findNavigationStartEventForFrameBeforeTimestamp(frameIdRef, | 
| + ts) { | 
| + var list = navigationStartsForFrameId[frameIdRef]; | 
| + if (list === undefined) | 
| + throw new Error('No navigationStartEvent found for frame id "' + | 
| + frameIdRef + '"'); | 
| + | 
| + var eventBeforeTimestamp; | 
| + list.forEach(function(ev) { | 
| + if (ev.start > ts) | 
| + return; | 
| + | 
| + if (eventBeforeTimestamp === undefined) | 
| + eventBeforeTimestamp = ev; | 
| + }, this); | 
| + if (eventBeforeTimestamp === undefined) | 
| + throw new Error('Failed to find navigationStartEvent.'); | 
| + return eventBeforeTimestamp; | 
| + } | 
| + } | 
| + | 
| + function findUrlOfFrameAt(rendererHelper, frameIdRef, ts) { | 
| + var url; | 
| + | 
| + var objects = rendererHelper.process.objects; | 
| + var frameLoaderInstances = objects.instancesByTypeName_['FrameLoader']; | 
| + frameLoaderInstances.forEach(function(instance) { | 
| + var snapshot = instance.getSnapshotAt(ts); | 
| + if (frameIdRef !== snapshot.args['frame']['id_ref']) | 
| + return; | 
| + | 
| + url = snapshot.args['documentLoaderURL']; | 
| + }, this); | 
| + | 
| + return url; | 
| } | 
| - function findFirstPaintEvent(rendererHelper, title, frame) { | 
| - var firstPaintEvent = undefined; | 
| + function findFirstPaintEvents(rendererHelper, title) { | 
| + var firstPaintEvents = []; | 
| rendererHelper.process.iterateAllEvents( | 
| function(ev) { | 
| - if (firstPaintEvent !== undefined || | 
| - ev.category !== 'blink.user_timing' || | 
| - ev.title !== title || | 
| - ev.args === undefined || ev.args['frame'] !== frame) | 
| + if (ev.category !== 'blink.user_timing' || | 
| + ev.title !== title) | 
| return; | 
| - firstPaintEvent = ev; | 
| + firstPaintEvents.push(ev); | 
| }, this); | 
| - return firstPaintEvent; | 
| + return firstPaintEvents; | 
| + } | 
| + | 
| + function prepareTelemetryInternalEventPredicate(rendererHelper) { | 
| + var ignoreRegionSlices = []; | 
| + rendererHelper.mainThread.asyncSliceGroup.iterateAllEventsInThisContainer( | 
| + () => true, function(slice) { | 
| + if (slice.title.match(/^telemetry\.internal/)) | 
| + ignoreRegionSlices.push(slice); | 
| + }, this); | 
| + | 
| + return function isTelemetryInternalEvent(slice) { | 
| + for (var i = 0; i < ignoreRegionSlices.length; ++ i) { | 
| + if (ignoreRegionSlices[i].bounds(slice)) | 
| + return true; | 
| + } | 
| + return false; | 
| + } | 
| } | 
| function firstPaintMetric(valueList, model) { | 
| - var rendererHelper = findTargetRendererHelper(model); | 
| - var navigationStartEvent = findNavigationStartEvent(rendererHelper); | 
| - | 
| - if (navigationStartEvent === undefined) | 
| - throw new Error('Failed to find navigationStartEvent.'); | 
| - | 
| - var frame = navigationStartEvent.args['frame']; | 
| - var firstContentfulPaintEvent = findFirstPaintEvent(rendererHelper, | 
| - 'firstContentfulPaint', frame); | 
| - if (firstContentfulPaintEvent === undefined) | 
| - throw new Error( | 
| - 'Failed to find firstContentfulPaintEvent for frame ' + frame); | 
| - | 
| - var grouping_keys = {}; | 
| - | 
| - var timeToFirstContentfulPaint = | 
| - firstContentfulPaintEvent.start - navigationStartEvent.start; | 
| - valueList.addValue(new tr.v.NumericValue( | 
| - model.canonicalUrlThatCreatedThisTrace, 'firstContentfulPaint', | 
| - new tr.v.ScalarNumeric(timeDurationInMs_smallerIsBetter, | 
| - timeToFirstContentfulPaint), | 
| - { description: 'time to first contentful paint' }, | 
| - grouping_keys)); | 
| + var chromeHelper = model.getOrCreateHelper( | 
| + tr.model.helpers.ChromeModelHelper); | 
| + var rendererHelper = findTargetRendererHelper(chromeHelper); | 
| + var isTelemetryInternalEvent = | 
| + prepareTelemetryInternalEventPredicate(rendererHelper); | 
| + var findNavigationStartEventForFrameBeforeTimestamp = | 
| + navigationStartFinder(rendererHelper); | 
| + | 
| + var firstPaintEvents = findFirstPaintEvents(rendererHelper, | 
| + 'firstContentfulPaint'); | 
| + firstPaintEvents = firstPaintEvents.filter( | 
| + (ev) => !isTelemetryInternalEvent(ev)); | 
| + firstPaintEvents.forEach(function(ev) { | 
| + var frameIdRef = ev.args['frame']; | 
| + var url = findUrlOfFrameAt(rendererHelper, frameIdRef, ev.start); | 
| + var navigationStartEvent = | 
| + findNavigationStartEventForFrameBeforeTimestamp(frameIdRef, ev.start); | 
| + | 
| + var timeToFirstContentfulPaint = | 
| + ev.start - navigationStartEvent.start; | 
| + | 
| + var grouping_keys = {url: url}; | 
| + valueList.addValue(new tr.v.NumericValue( | 
| 
benjhayden
2016/05/10 19:54:46
Instead of several scalars, do you want to use a N
 
kouhei (in TOK)
2016/05/11 06:39:33
I think we want separate individual values, as his
 
nednguyen
2016/05/11 21:36:38
Why do you think the histogram doesn't make sense?
 
kouhei (in TOK)
2016/05/12 05:41:25
Added Numeric histogram, but not sure how we can u
 
nednguyen
2016/05/12 15:35:30
For future use cases of Pcv2, I can imagine we hav
 | 
| + model.canonicalUrlThatCreatedThisTrace, 'firstContentfulPaint', | 
| 
benjhayden
2016/05/10 20:25:43
This is spelled "model.canonicalUrl" now.
 
kouhei (in TOK)
2016/05/11 06:39:33
Done.
 | 
| + new tr.v.ScalarNumeric(timeDurationInMs_smallerIsBetter, | 
| + timeToFirstContentfulPaint), | 
| + { description: 'time to first contentful paint' }, | 
| + grouping_keys)); | 
| + }, this); | 
| } | 
| firstPaintMetric.prototype = { |