Index: tools/android/loading/user_satisfied_lens.py |
diff --git a/tools/android/loading/user_satisfied_lens.py b/tools/android/loading/user_satisfied_lens.py |
index dd99d79a3a8b57a444670ecdf20f6c4e82cfb29c..523a240ece79497b531df2283b429dc34ced8822 100644 |
--- a/tools/android/loading/user_satisfied_lens.py |
+++ b/tools/android/loading/user_satisfied_lens.py |
@@ -6,6 +6,9 @@ |
Several lenses are defined, for example FirstTextPaintLens and |
FirstSignificantPaintLens. |
+ |
+When run from the command line, takes a lens name and a trace, and prints the |
+fingerprints of the critical resources to stdout. |
""" |
import logging |
import operator |
@@ -24,6 +27,53 @@ class _UserSatisfiedLens(object): |
_ATTRS = ['_satisfied_msec', '_event_msec', '_postload_msec', |
'_critical_request_ids'] |
+ def CriticalRequests(self): |
+ """Critical requests. |
+ |
+ Returns: |
+ A sequence of request_track.Request objects representing an estimate of |
+ all requests that are necessary for the user satisfaction defined by this |
+ class. |
+ """ |
+ raise NotImplementedError |
+ |
+ def CriticalRequestIds(self): |
+ """Ids of critical requests.""" |
+ return set(rq.request_id for rq in self.CriticalRequests()) |
+ |
+ def CriticalFingerprints(self): |
+ """Fingerprints of critical requests.""" |
+ return set(rq.fingerprint for rq in self.CriticalRequests()) |
+ |
+ def PostloadTimeMsec(self): |
+ """Return postload time. |
+ |
+ The postload time is an estimate of the amount of time needed by chrome to |
+ transform the critical results into the satisfying event. |
+ |
+ Returns: |
+ Postload time in milliseconds. |
+ """ |
+ return 0 |
+ |
+ |
+class RequestFingerprintLens(_UserSatisfiedLens): |
+ """A lens built using requests in a trace that match a set of fingerprints.""" |
+ def __init__(self, trace, fingerprints): |
+ fingerprints = set(fingerprints) |
+ self._critical_requests = [rq for rq in trace.request_track.GetEvents() |
+ if rq.fingerprint in fingerprints] |
+ |
+ def CriticalRequests(self): |
+ """Ids of critical requests.""" |
+ return set(self._critical_requests) |
+ |
+ |
+class _FirstEventLens(_UserSatisfiedLens): |
+ """Helper abstract subclass that defines users first event manipulations.""" |
+ # pylint can't handle abstract subclasses. |
+ # pylint: disable=abstract-method |
+ |
def __init__(self, trace): |
"""Initialize the lens. |
@@ -36,34 +86,23 @@ class _UserSatisfiedLens(object): |
self._critical_request_ids = None |
if trace is None: |
return |
- self._CalculateTimes(trace.tracing_track) |
- critical_requests = self._RequestsBefore( |
+ self._CalculateTimes(trace) |
+ self._critical_requests = self._RequestsBefore( |
trace.request_track, self._satisfied_msec) |
- self._critical_request_ids = set(rq.request_id for rq in critical_requests) |
- if critical_requests: |
- last_load = max(rq.end_msec for rq in critical_requests) |
+ self._critical_request_ids = set(rq.request_id |
+ for rq in self._critical_requests) |
+ if self._critical_requests: |
+ last_load = max(rq.end_msec for rq in self._critical_requests) |
else: |
last_load = float('inf') |
self._postload_msec = self._event_msec - last_load |
def CriticalRequests(self): |
- """Request ids of critical requests. |
- |
- Returns: |
- A set of request ids (as strings) of an estimate of all requests that are |
- necessary for the user satisfaction defined by this class. |
- """ |
- return self._critical_request_ids |
+ """Override.""" |
+ return self._critical_requests |
def PostloadTimeMsec(self): |
- """Return postload time. |
- |
- The postload time is an estimate of the amount of time needed by chrome to |
- transform the critical results into the satisfying event. |
- |
- Returns: |
- Postload time in milliseconds. |
- """ |
+ """Override.""" |
return self._postload_msec |
def ToJsonDict(self): |
@@ -75,7 +114,7 @@ class _UserSatisfiedLens(object): |
return common_util.DeserializeAttributesFromJsonDict( |
json_dict, result, cls._ATTRS) |
- def _CalculateTimes(self, tracing_track): |
+ def _CalculateTimes(self, trace): |
"""Subclasses should implement to set _satisfied_msec and _event_msec.""" |
raise NotImplementedError |
@@ -84,26 +123,19 @@ class _UserSatisfiedLens(object): |
return [rq for rq in request_track.GetEvents() |
if rq.end_msec <= time_ms] |
- |
-class _FirstEventLens(_UserSatisfiedLens): |
- """Helper abstract subclass that defines users first event manipulations.""" |
- # pylint can't handle abstract subclasses. |
- # pylint: disable=abstract-method |
- |
@classmethod |
def _CheckCategory(cls, tracing_track, category): |
assert category in tracing_track.Categories(), ( |
'The "%s" category must be enabled.' % category) |
@classmethod |
- def _ExtractFirstTiming(cls, times): |
+ def _ExtractBestTiming(cls, times): |
if not times: |
return float('inf') |
- if len(times) != 1: |
- # TODO(mattcary): in some cases a trace has two first paint events. Why? |
- logging.error('%d %s with spread of %s', len(times), |
- str(cls), max(times) - min(times)) |
- return float(min(times)) |
+ assert len(times) == 1, \ |
+ 'Unexpected duplicate {}: {} with spread of {}'.format( |
+ str(cls), len(times), max(times) - min(times)) |
+ return float(max(times)) |
class FirstTextPaintLens(_FirstEventLens): |
@@ -112,12 +144,13 @@ class FirstTextPaintLens(_FirstEventLens): |
This event is taken directly from a trace. |
""" |
_EVENT_CATEGORY = 'blink.user_timing' |
- def _CalculateTimes(self, tracing_track): |
- self._CheckCategory(tracing_track, self._EVENT_CATEGORY) |
- first_paints = [e.start_msec for e in tracing_track.GetEvents() |
- if e.Matches(self._EVENT_CATEGORY, 'firstPaint')] |
+ def _CalculateTimes(self, trace): |
+ self._CheckCategory(trace.tracing_track, self._EVENT_CATEGORY) |
+ first_paints = [ |
+ e.start_msec for e in trace.tracing_track.GetMatchingMainFrameEvents( |
+ 'blink.user_timing', 'firstPaint')] |
self._satisfied_msec = self._event_msec = \ |
- self._ExtractFirstTiming(first_paints) |
+ self._ExtractBestTiming(first_paints) |
class FirstContentfulPaintLens(_FirstEventLens): |
@@ -127,12 +160,13 @@ class FirstContentfulPaintLens(_FirstEventLens): |
by filtering out things like background paint from firstPaint. |
""" |
_EVENT_CATEGORY = 'blink.user_timing' |
- def _CalculateTimes(self, tracing_track): |
- self._CheckCategory(tracing_track, self._EVENT_CATEGORY) |
- first_paints = [e.start_msec for e in tracing_track.GetEvents() |
- if e.Matches(self._EVENT_CATEGORY, 'firstContentfulPaint')] |
+ def _CalculateTimes(self, trace): |
+ self._CheckCategory(trace.tracing_track, self._EVENT_CATEGORY) |
+ first_paints = [ |
+ e.start_msec for e in trace.tracing_track.GetMatchingMainFrameEvents( |
+ 'blink.user_timing', 'firstContentfulPaint')] |
self._satisfied_msec = self._event_msec = \ |
- self._ExtractFirstTiming(first_paints) |
+ self._ExtractBestTiming(first_paints) |
class FirstSignificantPaintLens(_FirstEventLens): |
@@ -143,12 +177,18 @@ class FirstSignificantPaintLens(_FirstEventLens): |
that is the observable event. |
""" |
_FIRST_LAYOUT_COUNTER = 'LayoutObjectsThatHadNeverHadLayout' |
- _EVENT_CATEGORY = 'disabled-by-default-blink.debug.layout' |
- def _CalculateTimes(self, tracing_track): |
- self._CheckCategory(tracing_track, self._EVENT_CATEGORY) |
+ _EVENT_CATEGORIES = ['blink', 'disabled-by-default-blink.debug.layout'] |
+ def _CalculateTimes(self, trace): |
+ for cat in self._EVENT_CATEGORIES: |
+ self._CheckCategory(trace.tracing_track, cat) |
sync_paint_times = [] |
layouts = [] # (layout item count, msec). |
- for e in tracing_track.GetEvents(): |
+ for e in trace.tracing_track.GetEvents(): |
+ if ('frame' in e.args and |
+ e.args['frame'] != trace.tracing_track.GetMainFrameID()): |
+ continue |
+ # If we don't know have a frame id, we assume it applies to all events. |
+ |
# TODO(mattcary): is this the right paint event? Check if synchronized |
# paints appear at the same time as the first*Paint events, above. |
if e.Matches('blink', 'FrameView::synchronizedPaint'): |
@@ -158,7 +198,25 @@ class FirstSignificantPaintLens(_FirstEventLens): |
layouts.append((e.args['counters'][self._FIRST_LAYOUT_COUNTER], |
e.start_msec)) |
assert layouts, 'No layout events' |
+ assert sync_paint_times,'No sync paint times' |
layouts.sort(key=operator.itemgetter(0), reverse=True) |
self._satisfied_msec = layouts[0][1] |
- self._event_msec = self._ExtractFirstTiming([ |
- min(t for t in sync_paint_times if t > self._satisfied_msec)]) |
+ self._event_msec = min(t for t in sync_paint_times |
+ if t > self._satisfied_msec) |
+ |
+ |
+def main(lens_name, trace_file): |
+ assert (lens_name in globals() and |
+ not lens_name.startswith('_') and |
+ lens_name.endswith('Lens')), 'Bad lens %s' % lens_name |
+ lens_cls = globals()[lens_name] |
+ trace = loading_trace.LoadingTrace.FromJsonFile(trace_file) |
+ lens = lens_cls(trace) |
+ for fp in sorted(lens.CriticalFingerprints()): |
+ print fp |
+ |
+ |
+if __name__ == '__main__': |
+ import sys |
+ import loading_trace |
+ main(sys.argv[1], sys.argv[2]) |