| 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 # All tracing categories not disabled-by-default |
| 15 DEFAULT_TRACE_CATEGORIES = None |
| 16 |
| 17 # Categories for absolute minimum overhead tracing. This contains no |
| 18 # sub-traces of thread tasks, so it's only useful for capturing the |
| 19 # cpu-time spent on threads (as well as needed benchmark traces) |
| 20 MINIMAL_TRACE_CATEGORIES = ("toplevel," |
| 21 "benchmark," |
| 22 "webkit.console," |
| 23 "trace_event_overhead") |
| 14 | 24 |
| 15 class MissingFramesError(page_measurement.MeasurementFailure): | 25 class MissingFramesError(page_measurement.MeasurementFailure): |
| 16 def __init__(self): | 26 def __init__(self): |
| 17 super(MissingFramesError, self).__init__( | 27 super(MissingFramesError, self).__init__( |
| 18 'No frames found in trace. Unable to normalize results.') | 28 'No frames found in trace. Unable to normalize results.') |
| 19 | 29 |
| 20 | 30 |
| 21 class TimelineMetric(Metric): | 31 class TimelineMetric(Metric): |
| 22 def __init__(self, mode): | 32 def __init__(self, mode): |
| 23 """ Initializes a TimelineMetric object. | 33 """ Initializes a TimelineMetric object. |
| 24 """ | 34 """ |
| 25 super(TimelineMetric, self).__init__() | 35 super(TimelineMetric, self).__init__() |
| 26 assert mode in (TRACING_MODE, TIMELINE_MODE) | 36 assert mode in (TRACING_MODE, TIMELINE_MODE) |
| 37 self.trace_categories = DEFAULT_TRACE_CATEGORIES |
| 27 self._mode = mode | 38 self._mode = mode |
| 28 self._model = None | 39 self._model = None |
| 29 self._renderer_process = None | 40 self._renderer_process = None |
| 30 self._actions = [] | 41 self._actions = [] |
| 31 self._action_ranges = [] | 42 self._action_ranges = [] |
| 32 | 43 |
| 33 def Start(self, page, tab): | 44 def Start(self, page, tab): |
| 34 """Starts gathering timeline data. | 45 """Starts gathering timeline data. |
| 35 | 46 |
| 36 mode: TRACING_MODE or TIMELINE_MODE | 47 mode: TRACING_MODE or TIMELINE_MODE |
| 37 """ | 48 """ |
| 38 self._model = None | 49 self._model = None |
| 39 if self._mode == TRACING_MODE: | 50 if self._mode == TRACING_MODE: |
| 40 if not tab.browser.supports_tracing: | 51 if not tab.browser.supports_tracing: |
| 41 raise Exception('Not supported') | 52 raise Exception('Not supported') |
| 42 tab.browser.StartTracing() | 53 tab.browser.StartTracing(self.trace_categories) |
| 43 else: | 54 else: |
| 44 assert self._mode == TIMELINE_MODE | 55 assert self._mode == TIMELINE_MODE |
| 45 tab.StartTimelineRecording() | 56 tab.StartTimelineRecording() |
| 46 | 57 |
| 47 def Stop(self, page, tab): | 58 def Stop(self, page, tab): |
| 48 if self._mode == TRACING_MODE: | 59 if self._mode == TRACING_MODE: |
| 49 trace_result = tab.browser.StopTracing() | 60 trace_result = tab.browser.StopTracing() |
| 50 self._model = trace_result.AsTimelineModel() | 61 self._model = trace_result.AsTimelineModel() |
| 51 self._renderer_process = self._model.GetRendererProcessFromTab(tab) | 62 self._renderer_process = self._model.GetRendererProcessFromTab(tab) |
| 52 self._action_ranges = [ action.GetActiveRangeOnTimeline(self._model) | 63 self._action_ranges = [ action.GetActiveRangeOnTimeline(self._model) |
| (...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 197 thread_category = TimelineThreadCategories[thread_name] | 208 thread_category = TimelineThreadCategories[thread_name] |
| 198 return thread_category | 209 return thread_category |
| 199 | 210 |
| 200 def ThreadTimeResultName(thread_category): | 211 def ThreadTimeResultName(thread_category): |
| 201 return "thread_" + thread_category + "_clock_time_per_frame" | 212 return "thread_" + thread_category + "_clock_time_per_frame" |
| 202 | 213 |
| 203 def ThreadCpuTimeResultName(thread_category): | 214 def ThreadCpuTimeResultName(thread_category): |
| 204 return "thread_" + thread_category + "_cpu_time_per_frame" | 215 return "thread_" + thread_category + "_cpu_time_per_frame" |
| 205 | 216 |
| 206 def ThreadDetailResultName(thread_category, detail): | 217 def ThreadDetailResultName(thread_category, detail): |
| 207 return "thread_" + thread_category + "|" + detail | 218 detail_sanitized = detail.replace('.','_') |
| 219 return "thread_" + thread_category + "|" + detail_sanitized |
| 208 | 220 |
| 209 | 221 |
| 210 class ResultsForThread(object): | 222 class ResultsForThread(object): |
| 211 def __init__(self, model, action_ranges, name): | 223 def __init__(self, model, action_ranges, name): |
| 212 self.model = model | 224 self.model = model |
| 213 self.toplevel_slices = [] | 225 self.toplevel_slices = [] |
| 214 self.all_slices = [] | 226 self.all_slices = [] |
| 215 self.name = name | 227 self.name = name |
| 216 self.action_ranges = action_ranges | 228 self.action_ranges = action_ranges |
| 217 | 229 |
| (...skipping 27 matching lines...) Expand all Loading... |
| 245 if action_range.Contains(bounds.Bounds.CreateFromEvent(event)): | 257 if action_range.Contains(bounds.Bounds.CreateFromEvent(event)): |
| 246 slices_in_actions.append(event) | 258 slices_in_actions.append(event) |
| 247 break | 259 break |
| 248 return slices_in_actions | 260 return slices_in_actions |
| 249 | 261 |
| 250 def AppendThreadSlices(self, thread): | 262 def AppendThreadSlices(self, thread): |
| 251 self.all_slices.extend(self.SlicesInActions(thread.all_slices)) | 263 self.all_slices.extend(self.SlicesInActions(thread.all_slices)) |
| 252 self.toplevel_slices.extend(self.SlicesInActions(thread.toplevel_slices)) | 264 self.toplevel_slices.extend(self.SlicesInActions(thread.toplevel_slices)) |
| 253 | 265 |
| 254 def AddResults(self, num_frames, results): | 266 def AddResults(self, num_frames, results): |
| 255 clock_report_name = ThreadTimeResultName(self.name) | 267 cpu_per_frame = (float(self.cpu_time) / num_frames) if num_frames else 0 |
| 256 cpu_report_name = ThreadCpuTimeResultName(self.name) | 268 results.Add(ThreadCpuTimeResultName(self.name), 'ms', cpu_per_frame) |
| 257 clock_per_frame = (float(self.clock_time) / num_frames) if num_frames else 0 | |
| 258 cpu_per_frame = (float(self.cpu_time) / num_frames) if num_frames else 0 | |
| 259 results.Add(clock_report_name, 'ms', clock_per_frame) | |
| 260 results.Add(cpu_report_name, 'ms', cpu_per_frame) | |
| 261 | 269 |
| 262 def AddDetailedResults(self, num_frames, results): | 270 def AddDetailedResults(self, num_frames, results): |
| 263 slices_by_category = collections.defaultdict(list) | 271 slices_by_category = collections.defaultdict(list) |
| 264 for s in self.all_slices: | 272 for s in self.all_slices: |
| 265 slices_by_category[s.category].append(s) | 273 slices_by_category[s.category].append(s) |
| 266 all_self_times = [] | 274 all_self_times = [] |
| 267 for category, slices_in_category in slices_by_category.iteritems(): | 275 for category, slices_in_category in slices_by_category.iteritems(): |
| 268 self_time = sum([x.self_time for x in slices_in_category]) | 276 self_time = sum([x.self_time for x in slices_in_category]) |
| 269 all_self_times.append(self_time) | 277 all_self_times.append(self_time) |
| 270 self_time_result = (float(self_time) / num_frames) if num_frames else 0 | 278 self_time_result = (float(self_time) / num_frames) if num_frames else 0 |
| 271 results.Add(ThreadDetailResultName(self.name, category), | 279 results.Add(ThreadDetailResultName(self.name, category), |
| 272 'ms', self_time_result) | 280 'ms', self_time_result) |
| 273 all_measured_time = sum(all_self_times) | 281 all_measured_time = sum(all_self_times) |
| 274 all_action_time = sum([action.bounds for action in self.action_ranges]) | 282 all_action_time = sum([action.bounds for action in self.action_ranges]) |
| 275 idle_time = max(0, all_action_time - all_measured_time) | 283 idle_time = max(0, all_action_time - all_measured_time) |
| 276 idle_time_result = (float(idle_time) / num_frames) if num_frames else 0 | 284 idle_time_result = (float(idle_time) / num_frames) if num_frames else 0 |
| 277 results.Add(ThreadDetailResultName(self.name, "idle"), | 285 results.Add(ThreadDetailResultName(self.name, "idle"), |
| 278 'ms', idle_time_result) | 286 'ms', idle_time_result) |
| 279 | 287 |
| 280 | 288 |
| 281 class ThreadTimesTimelineMetric(TimelineMetric): | 289 class ThreadTimesTimelineMetric(TimelineMetric): |
| 282 def __init__(self): | 290 def __init__(self): |
| 283 super(ThreadTimesTimelineMetric, self).__init__(TRACING_MODE) | 291 super(ThreadTimesTimelineMetric, self).__init__(TRACING_MODE) |
| 292 # Minimal traces, for minimum noise in CPU-time measurements. |
| 293 self.trace_categories = MINIMAL_TRACE_CATEGORIES |
| 284 self.results_to_report = AllThreads | 294 self.results_to_report = AllThreads |
| 285 self.details_to_report = NoThreads | 295 self.details_to_report = NoThreads |
| 286 | 296 |
| 297 def Start(self, page, tab): |
| 298 # We need the other traces in order to have any details to report. |
| 299 if not self.details_to_report == NoThreads: |
| 300 self.trace_categories = DEFAULT_TRACE_CATEGORIES |
| 301 super(ThreadTimesTimelineMetric, self).Start(page, tab) |
| 302 |
| 287 def CountSlices(self, slices, substring): | 303 def CountSlices(self, slices, substring): |
| 288 count = 0 | 304 count = 0 |
| 289 for event in slices: | 305 for event in slices: |
| 290 if substring in event.name: | 306 if substring in event.name: |
| 291 count += 1 | 307 count += 1 |
| 292 return count | 308 return count |
| 293 | 309 |
| 294 def AddResults(self, tab, results): | 310 def AddResults(self, tab, results): |
| 295 # We need at least one action or we won't count any slices. | 311 # We need at least one action or we won't count any slices. |
| 296 assert len(self._action_ranges) > 0 | 312 assert len(self._action_ranges) > 0 |
| (...skipping 24 matching lines...) Expand all Loading... |
| 321 num_frames = self.CountSlices(frame_slices, FrameTraceName) | 337 num_frames = self.CountSlices(frame_slices, FrameTraceName) |
| 322 | 338 |
| 323 # Report the desired results and details. | 339 # Report the desired results and details. |
| 324 for thread_results in thread_category_results.values(): | 340 for thread_results in thread_category_results.values(): |
| 325 if thread_results.name in self.results_to_report: | 341 if thread_results.name in self.results_to_report: |
| 326 thread_results.AddResults(num_frames, results) | 342 thread_results.AddResults(num_frames, results) |
| 327 # TOOD(nduca): When generic results objects are done, this special case | 343 # TOOD(nduca): When generic results objects are done, this special case |
| 328 # can be replaced with a generic UI feature. | 344 # can be replaced with a generic UI feature. |
| 329 if thread_results.name in self.details_to_report: | 345 if thread_results.name in self.details_to_report: |
| 330 thread_results.AddDetailedResults(num_frames, results) | 346 thread_results.AddDetailedResults(num_frames, results) |
| OLD | NEW |