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. 4 import collections 5 6 from metrics import Metric 7 8 TRACING_MODE = 'tracing-mode' 9 TIMELINE_MODE = 'timeline-mode' 10 11 class 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 58 class 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. 100 TimelineThreadCategories = { 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 114 def ThreadTimePercentageName(category): 115 return "thread_" + category + "_clock_time_percentage" 116 117 def ThreadCPUTimePercentageName(category): 118 return "thread_" + category + "_cpu_time_percentage" 119 120 class 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