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

Side by Side Diff: tools/android/loading/user_satisfied_lens.py

Issue 1888343003: Clovis: contentful paint upgrades. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@url-rename
Patch Set: rebase Created 4 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 unified diff | Download patch
OLDNEW
1 # Copyright 2016 The Chromium Authors. All rights reserved. 1 # Copyright 2016 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Identifies key events related to user satisfaction. 5 """Identifies key events related to user satisfaction.
6 6
7 Several lenses are defined, for example FirstTextPaintLens and 7 Several lenses are defined, for example FirstTextPaintLens and
8 FirstSignificantPaintLens. 8 FirstSignificantPaintLens.
9
10 When run from the command line, takes a lens name and a trace, and prints the
11 fingerprints of the critical resources to stdout.
9 """ 12 """
10 import logging 13 import logging
11 import operator 14 import operator
12 15
13 import common_util 16 import common_util
14 17
15 18
16 class _UserSatisfiedLens(object): 19 class _UserSatisfiedLens(object):
17 """A base class for all user satisfaction metrics. 20 """A base class for all user satisfaction metrics.
18 21
19 All of these work by identifying a user satisfaction event from the trace, and 22 All of these work by identifying a user satisfaction event from the trace, and
20 then building a set of request ids whose loading is needed to achieve that 23 then building a set of request ids whose loading is needed to achieve that
21 event. Subclasses need only provide the time computation. The base class will 24 event. Subclasses need only provide the time computation. The base class will
22 use that to construct the request ids. 25 use that to construct the request ids.
23 """ 26 """
24 _ATTRS = ['_satisfied_msec', '_event_msec', '_postload_msec', 27 _ATTRS = ['_satisfied_msec', '_event_msec', '_postload_msec',
25 '_critical_request_ids'] 28 '_critical_request_ids']
26 29
30 def CriticalRequests(self):
31 """Critical requests.
32
33 Returns:
34 A sequence of request_track.Request objects representing an estimate of
35 all requests that are necessary for the user satisfaction defined by this
36 class.
37 """
38 raise NotImplementedError
39
40 def CriticalRequestIds(self):
41 """Ids of critical requests."""
42 return set(rq.request_id for rq in self.CriticalRequests())
43
44 def CriticalFingerprints(self):
45 """Fingerprints of critical requests."""
46 return set(rq.fingerprint for rq in self.CriticalRequests())
47
48 def PostloadTimeMsec(self):
49 """Return postload time.
50
51 The postload time is an estimate of the amount of time needed by chrome to
52 transform the critical results into the satisfying event.
53
54 Returns:
55 Postload time in milliseconds.
56 """
57 return 0
58
59
60 class RequestFingerprintLens(_UserSatisfiedLens):
61 """A lens built using requests in a trace that match a set of fingerprints."""
62 def __init__(self, trace, fingerprints):
63 fingerprints = set(fingerprints)
64 self._critical_requests = [rq for rq in trace.request_track.GetEvents()
65 if rq.fingerprint in fingerprints]
66
67 def CriticalRequests(self):
68 """Ids of critical requests."""
69 return set(self._critical_requests)
70
71
72 class _FirstEventLens(_UserSatisfiedLens):
73 """Helper abstract subclass that defines users first event manipulations."""
74 # pylint can't handle abstract subclasses.
75 # pylint: disable=abstract-method
76
27 def __init__(self, trace): 77 def __init__(self, trace):
28 """Initialize the lens. 78 """Initialize the lens.
29 79
30 Args: 80 Args:
31 trace: (LoadingTrace) the trace to use in the analysis. 81 trace: (LoadingTrace) the trace to use in the analysis.
32 """ 82 """
33 self._satisfied_msec = None 83 self._satisfied_msec = None
34 self._event_msec = None 84 self._event_msec = None
35 self._postload_msec = None 85 self._postload_msec = None
36 self._critical_request_ids = None 86 self._critical_request_ids = None
37 if trace is None: 87 if trace is None:
38 return 88 return
39 self._CalculateTimes(trace.tracing_track) 89 self._CalculateTimes(trace)
40 critical_requests = self._RequestsBefore( 90 self._critical_requests = self._RequestsBefore(
41 trace.request_track, self._satisfied_msec) 91 trace.request_track, self._satisfied_msec)
42 self._critical_request_ids = set(rq.request_id for rq in critical_requests) 92 self._critical_request_ids = set(rq.request_id
43 if critical_requests: 93 for rq in self._critical_requests)
44 last_load = max(rq.end_msec for rq in critical_requests) 94 if self._critical_requests:
95 last_load = max(rq.end_msec for rq in self._critical_requests)
45 else: 96 else:
46 last_load = float('inf') 97 last_load = float('inf')
47 self._postload_msec = self._event_msec - last_load 98 self._postload_msec = self._event_msec - last_load
48 99
49 def CriticalRequests(self): 100 def CriticalRequests(self):
50 """Request ids of critical requests. 101 """Override."""
51 102 return self._critical_requests
52 Returns:
53 A set of request ids (as strings) of an estimate of all requests that are
54 necessary for the user satisfaction defined by this class.
55 """
56 return self._critical_request_ids
57 103
58 def PostloadTimeMsec(self): 104 def PostloadTimeMsec(self):
59 """Return postload time. 105 """Override."""
60
61 The postload time is an estimate of the amount of time needed by chrome to
62 transform the critical results into the satisfying event.
63
64 Returns:
65 Postload time in milliseconds.
66 """
67 return self._postload_msec 106 return self._postload_msec
68 107
69 def ToJsonDict(self): 108 def ToJsonDict(self):
70 return common_util.SerializeAttributesToJsonDict({}, self, self._ATTRS) 109 return common_util.SerializeAttributesToJsonDict({}, self, self._ATTRS)
71 110
72 @classmethod 111 @classmethod
73 def FromJsonDict(cls, json_dict): 112 def FromJsonDict(cls, json_dict):
74 result = cls(None) 113 result = cls(None)
75 return common_util.DeserializeAttributesFromJsonDict( 114 return common_util.DeserializeAttributesFromJsonDict(
76 json_dict, result, cls._ATTRS) 115 json_dict, result, cls._ATTRS)
77 116
78 def _CalculateTimes(self, tracing_track): 117 def _CalculateTimes(self, trace):
79 """Subclasses should implement to set _satisfied_msec and _event_msec.""" 118 """Subclasses should implement to set _satisfied_msec and _event_msec."""
80 raise NotImplementedError 119 raise NotImplementedError
81 120
82 @classmethod 121 @classmethod
83 def _RequestsBefore(cls, request_track, time_ms): 122 def _RequestsBefore(cls, request_track, time_ms):
84 return [rq for rq in request_track.GetEvents() 123 return [rq for rq in request_track.GetEvents()
85 if rq.end_msec <= time_ms] 124 if rq.end_msec <= time_ms]
86 125
87
88 class _FirstEventLens(_UserSatisfiedLens):
89 """Helper abstract subclass that defines users first event manipulations."""
90 # pylint can't handle abstract subclasses.
91 # pylint: disable=abstract-method
92
93 @classmethod 126 @classmethod
94 def _CheckCategory(cls, tracing_track, category): 127 def _CheckCategory(cls, tracing_track, category):
95 assert category in tracing_track.Categories(), ( 128 assert category in tracing_track.Categories(), (
96 'The "%s" category must be enabled.' % category) 129 'The "%s" category must be enabled.' % category)
97 130
98 @classmethod 131 @classmethod
99 def _ExtractFirstTiming(cls, times): 132 def _ExtractBestTiming(cls, times):
100 if not times: 133 if not times:
101 return float('inf') 134 return float('inf')
102 if len(times) != 1: 135 assert len(times) == 1, \
103 # TODO(mattcary): in some cases a trace has two first paint events. Why? 136 'Unexpected duplicate {}: {} with spread of {}'.format(
104 logging.error('%d %s with spread of %s', len(times), 137 str(cls), len(times), max(times) - min(times))
105 str(cls), max(times) - min(times)) 138 return float(max(times))
106 return float(min(times))
107 139
108 140
109 class FirstTextPaintLens(_FirstEventLens): 141 class FirstTextPaintLens(_FirstEventLens):
110 """Define satisfaction by the first text paint. 142 """Define satisfaction by the first text paint.
111 143
112 This event is taken directly from a trace. 144 This event is taken directly from a trace.
113 """ 145 """
114 _EVENT_CATEGORY = 'blink.user_timing' 146 _EVENT_CATEGORY = 'blink.user_timing'
115 def _CalculateTimes(self, tracing_track): 147 def _CalculateTimes(self, trace):
116 self._CheckCategory(tracing_track, self._EVENT_CATEGORY) 148 self._CheckCategory(trace.tracing_track, self._EVENT_CATEGORY)
117 first_paints = [e.start_msec for e in tracing_track.GetEvents() 149 first_paints = [
118 if e.Matches(self._EVENT_CATEGORY, 'firstPaint')] 150 e.start_msec for e in trace.tracing_track.GetMatchingMainFrameEvents(
151 'blink.user_timing', 'firstPaint')]
119 self._satisfied_msec = self._event_msec = \ 152 self._satisfied_msec = self._event_msec = \
120 self._ExtractFirstTiming(first_paints) 153 self._ExtractBestTiming(first_paints)
121 154
122 155
123 class FirstContentfulPaintLens(_FirstEventLens): 156 class FirstContentfulPaintLens(_FirstEventLens):
124 """Define satisfaction by the first contentful paint. 157 """Define satisfaction by the first contentful paint.
125 158
126 This event is taken directly from a trace. Internally to chrome it's computed 159 This event is taken directly from a trace. Internally to chrome it's computed
127 by filtering out things like background paint from firstPaint. 160 by filtering out things like background paint from firstPaint.
128 """ 161 """
129 _EVENT_CATEGORY = 'blink.user_timing' 162 _EVENT_CATEGORY = 'blink.user_timing'
130 def _CalculateTimes(self, tracing_track): 163 def _CalculateTimes(self, trace):
131 self._CheckCategory(tracing_track, self._EVENT_CATEGORY) 164 self._CheckCategory(trace.tracing_track, self._EVENT_CATEGORY)
132 first_paints = [e.start_msec for e in tracing_track.GetEvents() 165 first_paints = [
133 if e.Matches(self._EVENT_CATEGORY, 'firstContentfulPaint')] 166 e.start_msec for e in trace.tracing_track.GetMatchingMainFrameEvents(
167 'blink.user_timing', 'firstContentfulPaint')]
134 self._satisfied_msec = self._event_msec = \ 168 self._satisfied_msec = self._event_msec = \
135 self._ExtractFirstTiming(first_paints) 169 self._ExtractBestTiming(first_paints)
136 170
137 171
138 class FirstSignificantPaintLens(_FirstEventLens): 172 class FirstSignificantPaintLens(_FirstEventLens):
139 """Define satisfaction by the first paint after a big layout change. 173 """Define satisfaction by the first paint after a big layout change.
140 174
141 Our satisfaction time is that of the layout change, as all resources must have 175 Our satisfaction time is that of the layout change, as all resources must have
142 been loaded to compute the layout. Our event time is that of the next paint as 176 been loaded to compute the layout. Our event time is that of the next paint as
143 that is the observable event. 177 that is the observable event.
144 """ 178 """
145 _FIRST_LAYOUT_COUNTER = 'LayoutObjectsThatHadNeverHadLayout' 179 _FIRST_LAYOUT_COUNTER = 'LayoutObjectsThatHadNeverHadLayout'
146 _EVENT_CATEGORY = 'disabled-by-default-blink.debug.layout' 180 _EVENT_CATEGORIES = ['blink', 'disabled-by-default-blink.debug.layout']
147 def _CalculateTimes(self, tracing_track): 181 def _CalculateTimes(self, trace):
148 self._CheckCategory(tracing_track, self._EVENT_CATEGORY) 182 for cat in self._EVENT_CATEGORIES:
183 self._CheckCategory(trace.tracing_track, cat)
149 sync_paint_times = [] 184 sync_paint_times = []
150 layouts = [] # (layout item count, msec). 185 layouts = [] # (layout item count, msec).
151 for e in tracing_track.GetEvents(): 186 for e in trace.tracing_track.GetEvents():
187 if ('frame' in e.args and
188 e.args['frame'] != trace.tracing_track.GetMainFrameID()):
189 continue
190 # If we don't know have a frame id, we assume it applies to all events.
191
152 # TODO(mattcary): is this the right paint event? Check if synchronized 192 # TODO(mattcary): is this the right paint event? Check if synchronized
153 # paints appear at the same time as the first*Paint events, above. 193 # paints appear at the same time as the first*Paint events, above.
154 if e.Matches('blink', 'FrameView::synchronizedPaint'): 194 if e.Matches('blink', 'FrameView::synchronizedPaint'):
155 sync_paint_times.append(e.start_msec) 195 sync_paint_times.append(e.start_msec)
156 if ('counters' in e.args and 196 if ('counters' in e.args and
157 self._FIRST_LAYOUT_COUNTER in e.args['counters']): 197 self._FIRST_LAYOUT_COUNTER in e.args['counters']):
158 layouts.append((e.args['counters'][self._FIRST_LAYOUT_COUNTER], 198 layouts.append((e.args['counters'][self._FIRST_LAYOUT_COUNTER],
159 e.start_msec)) 199 e.start_msec))
160 assert layouts, 'No layout events' 200 assert layouts, 'No layout events'
201 assert sync_paint_times,'No sync paint times'
161 layouts.sort(key=operator.itemgetter(0), reverse=True) 202 layouts.sort(key=operator.itemgetter(0), reverse=True)
162 self._satisfied_msec = layouts[0][1] 203 self._satisfied_msec = layouts[0][1]
163 self._event_msec = self._ExtractFirstTiming([ 204 self._event_msec = min(t for t in sync_paint_times
164 min(t for t in sync_paint_times if t > self._satisfied_msec)]) 205 if t > self._satisfied_msec)
206
207
208 def main(lens_name, trace_file):
209 assert (lens_name in globals() and
210 not lens_name.startswith('_') and
211 lens_name.endswith('Lens')), 'Bad lens %s' % lens_name
212 lens_cls = globals()[lens_name]
213 trace = loading_trace.LoadingTrace.FromJsonFile(trace_file)
214 lens = lens_cls(trace)
215 for fp in sorted(lens.CriticalFingerprints()):
216 print fp
217
218
219 if __name__ == '__main__':
220 import sys
221 import loading_trace
222 main(sys.argv[1], sys.argv[2])
OLDNEW
« no previous file with comments | « tools/android/loading/tracing_unittest.py ('k') | tools/android/loading/user_satisfied_lens_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698