• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4import collections
5
6from metrics import Metric
7
8TRACING_MODE = 'tracing-mode'
9TIMELINE_MODE = 'timeline-mode'
10
11class TimelineMetric(Metric):
12  def __init__(self, mode):
13    ''' Initializes a TimelineMetric object.
14
15    mode: TRACING_MODE or TIMELINE_MODE
16    thread_filter: list of thread names to include.
17        Empty list or None includes all threads.
18        In TIMELINE_MODE, only 'thread 0' is available, which is the renderer
19        main thread.
20        In TRACING_MODE, the following renderer process threads are available:
21        'CrRendererMain', 'Chrome_ChildIOThread', and 'Compositor'.
22        CompositorRasterWorker threads are available with impl-side painting
23        enabled.
24    '''
25    assert mode in (TRACING_MODE, TIMELINE_MODE)
26    super(TimelineMetric, self).__init__()
27    self._mode = mode
28    self._model = None
29
30  def Start(self, page, tab):
31    self._model = None
32    if self._mode == TRACING_MODE:
33      if not tab.browser.supports_tracing:
34        raise Exception('Not supported')
35      tab.browser.StartTracing()
36    else:
37      assert self._mode == TIMELINE_MODE
38      tab.StartTimelineRecording()
39
40  def Stop(self, page, tab):
41    if self._mode == TRACING_MODE:
42      trace_result = tab.browser.StopTracing()
43      self._model = trace_result.AsTimelineModel()
44    else:
45      tab.StopTimelineRecording()
46      self._model = tab.timeline_model
47
48  def GetRendererProcess(self, tab):
49    if self._mode == TRACING_MODE:
50      return self._model.GetRendererProcessFromTab(tab)
51    else:
52      return self._model.GetAllProcesses()[0]
53
54  def AddResults(self, tab, results):
55    return
56
57
58class LoadTimesTimelineMetric(TimelineMetric):
59  def __init__(self, mode, thread_filter = None):
60    super(LoadTimesTimelineMetric, self).__init__(mode)
61    self._thread_filter = thread_filter
62
63  def AddResults(self, tab, results):
64    assert self._model
65
66    renderer_process = self.GetRendererProcess(tab)
67    events_by_name = collections.defaultdict(list)
68
69    for thread in renderer_process.threads.itervalues():
70
71      if self._thread_filter and not thread.name in self._thread_filter:
72        continue
73
74      thread_name = thread.name.replace('/','_')
75      events = thread.all_slices
76
77      for e in events:
78        events_by_name[e.name].append(e)
79
80      for event_name, event_group in events_by_name.iteritems():
81        times = [event.self_time for event in event_group]
82        total = sum(times)
83        biggest_jank = max(times)
84        full_name = thread_name + '|' + event_name
85        results.Add(full_name, 'ms', total)
86        results.Add(full_name + '_max', 'ms', biggest_jank)
87        results.Add(full_name + '_avg', 'ms', total / len(times))
88
89    for counter_name, counter in renderer_process.counters.iteritems():
90      total = sum(counter.totals)
91      results.Add(counter_name, 'count', total)
92      results.Add(counter_name + '_avg', 'count', total / len(counter.totals))
93
94
95# We want to generate a consistant picture of our thread usage, despite
96# having several process configurations (in-proc-gpu/single-proc).
97# Since we can't isolate renderer threads in single-process mode, we
98# always sum renderer-process threads' times. We also sum all io-threads
99# for simplicity.
100TimelineThreadCategories =  {
101  # These are matched exactly
102  "Chrome_InProcGpuThread": "GPU",
103  "CrGPUMain"             : "GPU",
104  "AsyncTransferThread"   : "GPU_transfer",
105  "CrBrowserMain"         : "browser_main",
106  "Browser Compositor"    : "browser_compositor",
107  "CrRendererMain"        : "renderer_main",
108  "Compositor"            : "renderer_compositor",
109  # These are matched by substring
110  "IOThread"              : "IO",
111  "CompositorRasterWorker": "raster"
112}
113
114def ThreadTimePercentageName(category):
115  return "thread_" + category + "_clock_time_percentage"
116
117def ThreadCPUTimePercentageName(category):
118  return "thread_" + category + "_cpu_time_percentage"
119
120class ThreadTimesTimelineMetric(TimelineMetric):
121  def __init__(self):
122    super(ThreadTimesTimelineMetric, self).__init__(TRACING_MODE)
123
124  def AddResults(self, tab, results):
125    # Default each category to zero for consistant results.
126    category_clock_times = collections.defaultdict(float)
127    category_cpu_times = collections.defaultdict(float)
128
129    for category in TimelineThreadCategories.values():
130      category_clock_times[category] = 0
131      category_cpu_times[category] = 0
132
133    # Add up thread time for all threads we care about.
134    for thread in self._model.GetAllThreads():
135      # First determine if we care about this thread.
136      # Check substrings first, followed by exact matches
137      thread_category = None
138      for substring, category in TimelineThreadCategories.iteritems():
139        if substring in thread.name:
140          thread_category = category
141      if thread.name in TimelineThreadCategories:
142        thread_category = TimelineThreadCategories[thread.name]
143      if thread_category == None:
144        thread_category = "other"
145
146      # Sum and add top-level slice durations
147      clock = sum([event.duration for event in thread.toplevel_slices])
148      category_clock_times[thread_category] += clock
149      cpu = sum([event.thread_duration for event in thread.toplevel_slices])
150      category_cpu_times[thread_category] += cpu
151
152    # Now report each category. We report the percentage of time that
153    # the thread is running rather than absolute time, to represent how
154    # busy the thread is. This needs to be interpretted when throughput
155    # is changed due to scheduling changes (eg. more frames produced
156    # in the same time period). It would be nice if we could correct
157    # for that somehow.
158    for category, category_time in category_clock_times.iteritems():
159      report_name = ThreadTimePercentageName(category)
160      time_as_percentage = (category_time / self._model.bounds.bounds) * 100
161      results.Add(report_name, '%', time_as_percentage)
162
163    # Do the same for CPU (scheduled) time.
164    for category, category_time in category_cpu_times.iteritems():
165      report_name = ThreadCPUTimePercentageName(category)
166      time_as_percentage = (category_time / self._model.bounds.bounds) * 100
167      results.Add(report_name, '%', time_as_percentage)
168