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

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: Use new frame detection 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()
Benoit L 2016/04/27 12:40:50 The fingerprint is constructed from the URL. What
mattcary 2016/04/27 12:47:55 It's counted as the same request. I wasn't sure wh
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 _ExtractFirstTiming(cls, times): 127 def _ExtractBestTiming(cls, times):
95 if not times: 128 if not times:
96 return float('inf') 129 return float('inf')
97 if len(times) != 1: 130 assert len(times) == 1, \
98 # TODO(mattcary): in some cases a trace has two first paint events. Why? 131 'Unexpected duplicate {}: {} with spread of {}'.format(
99 logging.error('%d %s with spread of %s', len(times), 132 str(cls), len(times), max(times) - min(times))
100 str(cls), max(times) - min(times)) 133 return float(max(times))
101 return float(min(times))
102 134
103 135
104 class FirstTextPaintLens(_FirstEventLens): 136 class FirstTextPaintLens(_FirstEventLens):
105 """Define satisfaction by the first text paint. 137 """Define satisfaction by the first text paint.
106 138
107 This event is taken directly from a trace. 139 This event is taken directly from a trace.
108 """ 140 """
109 def _CalculateTimes(self, tracing_track): 141 def _CalculateTimes(self, trace):
110 first_paints = [e.start_msec for e in tracing_track.GetEvents() 142 first_paints = [
111 if e.Matches('blink.user_timing', 'firstPaint')] 143 e.start_msec for e in trace.tracing_track.GetMatchingMainFrameEvents(
144 'blink.user_timing', 'firstPaint')]
112 self._satisfied_msec = self._event_msec = \ 145 self._satisfied_msec = self._event_msec = \
113 self._ExtractFirstTiming(first_paints) 146 self._ExtractBestTiming(first_paints)
114 147
115 148
116 class FirstContentfulPaintLens(_FirstEventLens): 149 class FirstContentfulPaintLens(_FirstEventLens):
117 """Define satisfaction by the first contentful paint. 150 """Define satisfaction by the first contentful paint.
118 151
119 This event is taken directly from a trace. Internally to chrome it's computed 152 This event is taken directly from a trace. Internally to chrome it's computed
120 by filtering out things like background paint from firstPaint. 153 by filtering out things like background paint from firstPaint.
121 """ 154 """
122 def _CalculateTimes(self, tracing_track): 155 def _CalculateTimes(self, trace):
123 first_paints = [e.start_msec for e in tracing_track.GetEvents() 156 first_paints = [
124 if e.Matches('blink.user_timing', 'firstContentfulPaint')] 157 e.start_msec for e in trace.tracing_track.GetMatchingMainFrameEvents(
158 'blink.user_timing', 'firstContentfulPaint')]
125 self._satisfied_msec = self._event_msec = \ 159 self._satisfied_msec = self._event_msec = \
126 self._ExtractFirstTiming(first_paints) 160 self._ExtractBestTiming(first_paints)
127 161
128 162
129 class FirstSignificantPaintLens(_FirstEventLens): 163 class FirstSignificantPaintLens(_FirstEventLens):
130 """Define satisfaction by the first paint after a big layout change. 164 """Define satisfaction by the first paint after a big layout change.
131 165
132 Our satisfaction time is that of the layout change, as all resources must have 166 Our satisfaction time is that of the layout change, as all resources must have
133 been loaded to compute the layout. Our event time is that of the next paint as 167 been loaded to compute the layout. Our event time is that of the next paint as
134 that is the observable event. 168 that is the observable event.
135 """ 169 """
136 FIRST_LAYOUT_COUNTER = 'LayoutObjectsThatHadNeverHadLayout' 170 FIRST_LAYOUT_COUNTER = 'LayoutObjectsThatHadNeverHadLayout'
137 171
138 def _CalculateTimes(self, tracing_track): 172 def _CalculateTimes(self, trace):
139 sync_paint_times = [] 173 sync_paint_times = []
140 layouts = [] # (layout item count, msec). 174 layouts = [] # (layout item count, msec).
141 for e in tracing_track.GetEvents(): 175 for e in trace.tracing_track.GetEvents():
176 if ('frame' in e.args and
177 e.args['frame'] != trace.tracing_track.GetMainFrameID()):
178 continue
179 # If we don't know have a frame id, we assume it applies to all events.
180
142 # TODO(mattcary): is this the right paint event? Check if synchronized 181 # TODO(mattcary): is this the right paint event? Check if synchronized
143 # paints appear at the same time as the first*Paint events, above. 182 # paints appear at the same time as the first*Paint events, above.
144 if e.Matches('blink', 'FrameView::SynchronizedPaint'): 183 if e.Matches('blink', 'FrameView::SynchronizedPaint'):
145 sync_paint_times.append(e.start_msec) 184 sync_paint_times.append(e.start_msec)
146 if ('counters' in e.args and 185 if ('counters' in e.args and
147 self.FIRST_LAYOUT_COUNTER in e.args['counters']): 186 self.FIRST_LAYOUT_COUNTER in e.args['counters']):
148 layouts.append((e.args['counters'][self.FIRST_LAYOUT_COUNTER], 187 layouts.append((e.args['counters'][self.FIRST_LAYOUT_COUNTER],
149 e.start_msec)) 188 e.start_msec))
150 assert layouts, ('No layout events, was the disabled-by-default-blink' 189 assert layouts, ('No layout events, was the disabled-by-default-blink'
151 '.debug.layout category enabled?') 190 '.debug.layout category enabled?')
191 assert sync_paint_times, \
192 'No sync paint times, was the blink category disabled?'
152 layouts.sort(key=operator.itemgetter(0), reverse=True) 193 layouts.sort(key=operator.itemgetter(0), reverse=True)
153 self._satisfied_msec = layouts[0][1] 194 self._satisfied_msec = layouts[0][1]
154 self._event_msec = self._ExtractFirstTiming([ 195 self._event_msec = min(t for t in sync_paint_times
155 min(t for t in sync_paint_times if t > self._satisfied_msec)]) 196 if t > self._satisfied_msec)
197
198
199 def main(lens_name, trace_file):
200 assert (lens_name in globals() and
201 not lens_name.startswith('_') and
202 lens_name.endswith('Lens')), 'Bad lens %s' % lens_name
203 lens_cls = globals()[lens_name]
204 trace = loading_trace.LoadingTrace.FromJsonFile(trace_file)
205 lens = lens_cls(trace)
206 for fp in sorted(lens.CriticalFingerprints()):
207 print fp
208
209
210 if __name__ == '__main__':
211 import sys
212 import loading_trace
213 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