| 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 |