OLD | NEW |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 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 import collections | 4 import collections |
5 import itertools | 5 import itertools |
6 | 6 |
7 from metrics import Metric | 7 from metrics import Metric |
8 from telemetry.core.timeline import bounds | 8 from telemetry.core.timeline import bounds |
9 from telemetry.page import page_measurement | 9 from telemetry.page import page_measurement |
10 | 10 |
11 TRACING_MODE = 'tracing-mode' | 11 TRACING_MODE = 'tracing-mode' |
12 TIMELINE_MODE = 'timeline-mode' | 12 TIMELINE_MODE = 'timeline-mode' |
13 | 13 |
| 14 |
14 class MissingFramesError(page_measurement.MeasurementFailure): | 15 class MissingFramesError(page_measurement.MeasurementFailure): |
15 def __init__(self): | 16 def __init__(self): |
16 super(MissingFramesError, self).__init__( | 17 super(MissingFramesError, self).__init__( |
17 'No frames found in trace. Unable to normalize results.') | 18 'No frames found in trace. Unable to normalize results.') |
18 | 19 |
| 20 |
19 class TimelineMetric(Metric): | 21 class TimelineMetric(Metric): |
20 def __init__(self, mode): | 22 def __init__(self, mode): |
21 """ Initializes a TimelineMetric object. | 23 """ Initializes a TimelineMetric object. |
22 """ | 24 """ |
23 super(TimelineMetric, self).__init__() | 25 super(TimelineMetric, self).__init__() |
24 assert mode in (TRACING_MODE, TIMELINE_MODE) | 26 assert mode in (TRACING_MODE, TIMELINE_MODE) |
25 self._mode = mode | 27 self._mode = mode |
26 self._model = None | 28 self._model = None |
27 self._renderer_process = None | 29 self._renderer_process = None |
28 self._actions = [] | 30 self._actions = [] |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
72 def renderer_process(self): | 74 def renderer_process(self): |
73 return self._renderer_process | 75 return self._renderer_process |
74 | 76 |
75 @renderer_process.setter | 77 @renderer_process.setter |
76 def renderer_process(self, p): | 78 def renderer_process(self, p): |
77 self._renderer_process = p | 79 self._renderer_process = p |
78 | 80 |
79 def AddResults(self, tab, results): | 81 def AddResults(self, tab, results): |
80 return | 82 return |
81 | 83 |
| 84 |
82 class LoadTimesTimelineMetric(TimelineMetric): | 85 class LoadTimesTimelineMetric(TimelineMetric): |
83 def __init__(self, mode): | 86 def __init__(self, mode): |
84 super(LoadTimesTimelineMetric, self).__init__(mode) | 87 super(LoadTimesTimelineMetric, self).__init__(mode) |
85 self.report_main_thread_only = True | 88 self.report_main_thread_only = True |
86 | 89 |
87 def AddResults(self, _, results): | 90 def AddResults(self, _, results): |
88 assert self._model | 91 assert self._model |
89 if self.report_main_thread_only: | 92 if self.report_main_thread_only: |
90 if self._mode == TRACING_MODE: | 93 if self._mode == TRACING_MODE: |
91 thread_filter = 'CrRendererMain' | 94 thread_filter = 'CrRendererMain' |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
133 | 136 |
134 # We want to generate a consistant picture of our thread usage, despite | 137 # We want to generate a consistant picture of our thread usage, despite |
135 # having several process configurations (in-proc-gpu/single-proc). | 138 # having several process configurations (in-proc-gpu/single-proc). |
136 # Since we can't isolate renderer threads in single-process mode, we | 139 # Since we can't isolate renderer threads in single-process mode, we |
137 # always sum renderer-process threads' times. We also sum all io-threads | 140 # always sum renderer-process threads' times. We also sum all io-threads |
138 # for simplicity. | 141 # for simplicity. |
139 TimelineThreadCategories = { | 142 TimelineThreadCategories = { |
140 "Chrome_InProcGpuThread": "GPU", | 143 "Chrome_InProcGpuThread": "GPU", |
141 "CrGpuMain" : "GPU", | 144 "CrGpuMain" : "GPU", |
142 "AsyncTransferThread" : "GPU_transfer", | 145 "AsyncTransferThread" : "GPU_transfer", |
143 "CrBrowserMain" : "browser_main", | 146 "CrBrowserMain" : "browser", |
144 "Browser Compositor" : "browser_compositor", | 147 "Browser Compositor" : "browser", |
145 "CrRendererMain" : "renderer_main", | 148 "CrRendererMain" : "renderer_main", |
146 "Compositor" : "renderer_compositor", | 149 "Compositor" : "renderer_compositor", |
147 "IOThread" : "IO", | 150 "IOThread" : "IO", |
148 "CompositorRasterWorker": "raster", | 151 "CompositorRasterWorker": "raster", |
149 "DummyThreadName1" : "other", | 152 "DummyThreadName1" : "other", |
150 "DummyThreadName2" : "total_fast_path", | 153 "DummyThreadName2" : "total_fast_path", |
151 "DummyThreadName3" : "total_all" | 154 "DummyThreadName3" : "total_all" |
152 } | 155 } |
153 | 156 |
154 MatchBySubString = ["IOThread", "CompositorRasterWorker"] | 157 _MatchBySubString = ["IOThread", "CompositorRasterWorker"] |
155 | 158 |
156 AllThreads = TimelineThreadCategories.values() | 159 AllThreads = TimelineThreadCategories.values() |
157 NoThreads = [] | 160 NoThreads = [] |
158 FastPathThreads = ["GPU", | 161 FastPathThreads = ["GPU", "renderer_compositor", "browser", "IO"] |
159 "browser_main", | 162 |
160 "browser_compositor", | 163 ReportMainThreadOnly = ["renderer_main"] |
161 "renderer_compositor", | 164 ReportFastPathResults = AllThreads |
162 "IO"] | 165 ReportFastPathDetails = NoThreads |
163 MainThread = ["renderer_main"] | 166 ReportSilkResults = ["renderer_main", "total_all"] |
164 FastPathResults = AllThreads | 167 ReportSilkDetails = ["renderer_main"] |
165 FastPathDetails = NoThreads | |
166 SilkResults = ["renderer_main", "total_all"] | |
167 SilkDetails = MainThread | |
168 | 168 |
169 # TODO(epenner): Thread names above are likely fairly stable but trace names | 169 # TODO(epenner): Thread names above are likely fairly stable but trace names |
170 # could change. We should formalize this trace to keep this robust. | 170 # could change. We should formalize these traces to keep this robust. |
171 CompositorFrameTraceName = "::SwapBuffers" | 171 OverheadTraceCategory = "trace_event_overhead" |
| 172 OverheadTraceName = "overhead" |
| 173 FrameTraceName = "::SwapBuffers" |
| 174 FrameTraceThreadName = "renderer_compositor" |
| 175 |
| 176 |
| 177 def ClockOverheadForEvent(event): |
| 178 if (event.category == OverheadTraceCategory and |
| 179 event.name == OverheadTraceName): |
| 180 return event.duration |
| 181 else: |
| 182 return 0 |
| 183 |
| 184 def CpuOverheadForEvent(event): |
| 185 if (event.category == OverheadTraceCategory and |
| 186 event.thread_duration): |
| 187 return event.thread_duration |
| 188 else: |
| 189 return 0 |
172 | 190 |
173 def ThreadCategoryName(thread_name): | 191 def ThreadCategoryName(thread_name): |
174 thread_category = "other" | 192 thread_category = "other" |
175 for substring, category in TimelineThreadCategories.iteritems(): | 193 for substring, category in TimelineThreadCategories.iteritems(): |
176 if substring in MatchBySubString and substring in thread_name: | 194 if substring in _MatchBySubString and substring in thread_name: |
177 thread_category = category | 195 thread_category = category |
178 if thread_name in TimelineThreadCategories: | 196 if thread_name in TimelineThreadCategories: |
179 thread_category = TimelineThreadCategories[thread_name] | 197 thread_category = TimelineThreadCategories[thread_name] |
180 return thread_category | 198 return thread_category |
181 | 199 |
182 def ThreadTimeResultName(thread_category): | 200 def ThreadTimeResultName(thread_category): |
183 return "thread_" + thread_category + "_clock_time_per_frame" | 201 return "thread_" + thread_category + "_clock_time_per_frame" |
184 | 202 |
185 def ThreadCpuTimeResultName(thread_category): | 203 def ThreadCpuTimeResultName(thread_category): |
186 return "thread_" + thread_category + "_cpu_time_per_frame" | 204 return "thread_" + thread_category + "_cpu_time_per_frame" |
187 | 205 |
188 def ThreadDetailResultName(thread_category, detail): | 206 def ThreadDetailResultName(thread_category, detail): |
189 return "thread_" + thread_category + "|" + detail | 207 return "thread_" + thread_category + "|" + detail |
190 | 208 |
| 209 |
191 class ResultsForThread(object): | 210 class ResultsForThread(object): |
192 def __init__(self, model, action_ranges, name): | 211 def __init__(self, model, action_ranges, name): |
193 self.model = model | 212 self.model = model |
194 self.toplevel_slices = [] | 213 self.toplevel_slices = [] |
195 self.all_slices = [] | 214 self.all_slices = [] |
196 self.name = name | 215 self.name = name |
197 self.action_ranges = action_ranges | 216 self.action_ranges = action_ranges |
198 | 217 |
199 @property | 218 @property |
200 def clock_time(self): | 219 def clock_time(self): |
201 return sum([x.duration for x in self.toplevel_slices]) | 220 clock_duration = sum([x.duration for x in self.toplevel_slices]) |
| 221 clock_overhead = sum([ClockOverheadForEvent(x) for x in self.all_slices]) |
| 222 return clock_duration - clock_overhead |
202 | 223 |
203 @property | 224 @property |
204 def cpu_time(self): | 225 def cpu_time(self): |
205 res = 0 | 226 cpu_duration = 0 |
| 227 cpu_overhead = sum([CpuOverheadForEvent(x) for x in self.all_slices]) |
206 for x in self.toplevel_slices: | 228 for x in self.toplevel_slices: |
207 # Only report thread-duration if we have it for all events. | 229 # Only report thread-duration if we have it for all events. |
208 # | 230 # |
209 # A thread_duration of 0 is valid, so this only returns 0 if it is None. | 231 # A thread_duration of 0 is valid, so this only returns 0 if it is None. |
210 if x.thread_duration == None: | 232 if x.thread_duration == None: |
211 if not x.duration: | 233 if not x.duration: |
212 continue | 234 continue |
213 else: | 235 else: |
214 return 0 | 236 return 0 |
215 else: | 237 else: |
216 res += x.thread_duration | 238 cpu_duration += x.thread_duration |
217 return res | 239 return cpu_duration - cpu_overhead |
218 | 240 |
219 def ActionSlices(self, slices): | 241 def SlicesInActions(self, slices): |
220 slices_in_actions = [] | 242 slices_in_actions = [] |
221 for event in slices: | 243 for event in slices: |
222 for action_range in self.action_ranges: | 244 for action_range in self.action_ranges: |
223 if action_range.Contains(bounds.Bounds.CreateFromEvent(event)): | 245 if action_range.Contains(bounds.Bounds.CreateFromEvent(event)): |
224 slices_in_actions.append(event) | 246 slices_in_actions.append(event) |
225 break | 247 break |
226 return slices_in_actions | 248 return slices_in_actions |
227 | 249 |
228 def AppendThreadSlices(self, thread): | 250 def AppendThreadSlices(self, thread): |
229 self.all_slices.extend(self.ActionSlices(thread.all_slices)) | 251 self.all_slices.extend(self.SlicesInActions(thread.all_slices)) |
230 self.toplevel_slices.extend(self.ActionSlices(thread.toplevel_slices)) | 252 self.toplevel_slices.extend(self.SlicesInActions(thread.toplevel_slices)) |
231 | 253 |
232 def AddResults(self, num_frames, results): | 254 def AddResults(self, num_frames, results): |
233 clock_report_name = ThreadTimeResultName(self.name) | 255 clock_report_name = ThreadTimeResultName(self.name) |
234 cpu_report_name = ThreadCpuTimeResultName(self.name) | 256 cpu_report_name = ThreadCpuTimeResultName(self.name) |
235 clock_per_frame = (float(self.clock_time) / num_frames) if num_frames else 0 | 257 clock_per_frame = (float(self.clock_time) / num_frames) if num_frames else 0 |
236 cpu_per_frame = (float(self.cpu_time) / num_frames) if num_frames else 0 | 258 cpu_per_frame = (float(self.cpu_time) / num_frames) if num_frames else 0 |
237 results.Add(clock_report_name, 'ms', clock_per_frame) | 259 results.Add(clock_report_name, 'ms', clock_per_frame) |
238 results.Add(cpu_report_name, 'ms', cpu_per_frame) | 260 results.Add(cpu_report_name, 'ms', cpu_per_frame) |
239 | 261 |
240 def AddDetailedResults(self, num_frames, results): | 262 def AddDetailedResults(self, num_frames, results): |
241 slices_by_category = collections.defaultdict(list) | 263 slices_by_category = collections.defaultdict(list) |
242 for s in self.all_slices: | 264 for s in self.all_slices: |
243 slices_by_category[s.category].append(s) | 265 slices_by_category[s.category].append(s) |
244 all_self_times = [] | 266 all_self_times = [] |
245 for category, slices_in_category in slices_by_category.iteritems(): | 267 for category, slices_in_category in slices_by_category.iteritems(): |
246 self_time = sum([x.self_time for x in slices_in_category]) | 268 self_time = sum([x.self_time for x in slices_in_category]) |
247 all_self_times.append(self_time) | 269 all_self_times.append(self_time) |
248 self_time_result = (float(self_time) / num_frames) if num_frames else 0 | 270 self_time_result = (float(self_time) / num_frames) if num_frames else 0 |
249 results.Add(ThreadDetailResultName(self.name, category), | 271 results.Add(ThreadDetailResultName(self.name, category), |
250 'ms', self_time_result) | 272 'ms', self_time_result) |
251 all_measured_time = sum(all_self_times) | 273 all_measured_time = sum(all_self_times) |
252 all_action_time = sum([action.bounds for action in self.action_ranges]) | 274 all_action_time = sum([action.bounds for action in self.action_ranges]) |
253 idle_time = max(0, all_action_time - all_measured_time) | 275 idle_time = max(0, all_action_time - all_measured_time) |
254 idle_time_result = (float(idle_time) / num_frames) if num_frames else 0 | 276 idle_time_result = (float(idle_time) / num_frames) if num_frames else 0 |
255 results.Add(ThreadDetailResultName(self.name, "idle"), | 277 results.Add(ThreadDetailResultName(self.name, "idle"), |
256 'ms', idle_time_result) | 278 'ms', idle_time_result) |
257 | 279 |
| 280 |
258 class ThreadTimesTimelineMetric(TimelineMetric): | 281 class ThreadTimesTimelineMetric(TimelineMetric): |
259 def __init__(self): | 282 def __init__(self): |
260 super(ThreadTimesTimelineMetric, self).__init__(TRACING_MODE) | 283 super(ThreadTimesTimelineMetric, self).__init__(TRACING_MODE) |
261 self.results_to_report = AllThreads | 284 self.results_to_report = AllThreads |
262 self.details_to_report = NoThreads | 285 self.details_to_report = NoThreads |
263 | 286 |
264 def CountSlices(self, slices, substring): | 287 def CountSlices(self, slices, substring): |
265 count = 0 | 288 count = 0 |
266 for event in slices: | 289 for event in slices: |
267 if substring in event.name: | 290 if substring in event.name: |
(...skipping 18 matching lines...) Expand all Loading... |
286 | 309 |
287 # Group all threads. | 310 # Group all threads. |
288 for thread in self._model.GetAllThreads(): | 311 for thread in self._model.GetAllThreads(): |
289 thread_category_results['total_all'].AppendThreadSlices(thread) | 312 thread_category_results['total_all'].AppendThreadSlices(thread) |
290 | 313 |
291 # Also group fast-path threads. | 314 # Also group fast-path threads. |
292 for thread in self._model.GetAllThreads(): | 315 for thread in self._model.GetAllThreads(): |
293 if ThreadCategoryName(thread.name) in FastPathThreads: | 316 if ThreadCategoryName(thread.name) in FastPathThreads: |
294 thread_category_results['total_fast_path'].AppendThreadSlices(thread) | 317 thread_category_results['total_fast_path'].AppendThreadSlices(thread) |
295 | 318 |
296 # Calculate the number of frames from the CC thread. | 319 # Calculate the number of frames. |
297 cc_slices = thread_category_results['renderer_compositor'].all_slices | 320 frame_slices = thread_category_results[FrameTraceThreadName].all_slices |
298 num_frames = self.CountSlices(cc_slices, CompositorFrameTraceName) | 321 num_frames = self.CountSlices(frame_slices, FrameTraceName) |
299 | 322 |
300 # Report the desired results and details. | 323 # Report the desired results and details. |
301 for thread_results in thread_category_results.values(): | 324 for thread_results in thread_category_results.values(): |
302 if thread_results.name in self.results_to_report: | 325 if thread_results.name in self.results_to_report: |
303 thread_results.AddResults(num_frames, results) | 326 thread_results.AddResults(num_frames, results) |
304 # TOOD(nduca): When generic results objects are done, this special case | 327 # TOOD(nduca): When generic results objects are done, this special case |
305 # can be replaced with a generic UI feature. | 328 # can be replaced with a generic UI feature. |
306 if thread_results.name in self.details_to_report: | 329 if thread_results.name in self.details_to_report: |
307 thread_results.AddDetailedResults(num_frames, results) | 330 thread_results.AddDetailedResults(num_frames, results) |
OLD | NEW |