| 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 | 5 |
| 6 from metrics import Metric | 6 from metrics import Metric |
| 7 from telemetry.page import page_measurement |
| 7 | 8 |
| 8 TRACING_MODE = 'tracing-mode' | 9 TRACING_MODE = 'tracing-mode' |
| 9 TIMELINE_MODE = 'timeline-mode' | 10 TIMELINE_MODE = 'timeline-mode' |
| 10 | 11 |
| 12 class MissingFramesError(page_measurement.MeasurementFailure): |
| 13 def __init__(self): |
| 14 super(MissingFramesError, self).__init__( |
| 15 'No frames found in trace. Unable to normalize results.') |
| 16 |
| 11 class TimelineMetric(Metric): | 17 class TimelineMetric(Metric): |
| 12 def __init__(self, mode): | 18 def __init__(self, mode): |
| 13 """ Initializes a TimelineMetric object. | 19 """ Initializes a TimelineMetric object. |
| 14 """ | 20 """ |
| 15 super(TimelineMetric, self).__init__() | 21 super(TimelineMetric, self).__init__() |
| 16 assert mode in (TRACING_MODE, TIMELINE_MODE) | 22 assert mode in (TRACING_MODE, TIMELINE_MODE) |
| 17 self._mode = mode | 23 self._mode = mode |
| 18 self._model = None | 24 self._model = None |
| 19 self._renderer_process = None | 25 self._renderer_process = None |
| 20 | 26 |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 107 # for simplicity. | 113 # for simplicity. |
| 108 TimelineThreadCategories = { | 114 TimelineThreadCategories = { |
| 109 "Chrome_InProcGpuThread": "GPU", | 115 "Chrome_InProcGpuThread": "GPU", |
| 110 "CrGPUMain" : "GPU", | 116 "CrGPUMain" : "GPU", |
| 111 "AsyncTransferThread" : "GPU_transfer", | 117 "AsyncTransferThread" : "GPU_transfer", |
| 112 "CrBrowserMain" : "browser_main", | 118 "CrBrowserMain" : "browser_main", |
| 113 "Browser Compositor" : "browser_compositor", | 119 "Browser Compositor" : "browser_compositor", |
| 114 "CrRendererMain" : "renderer_main", | 120 "CrRendererMain" : "renderer_main", |
| 115 "Compositor" : "renderer_compositor", | 121 "Compositor" : "renderer_compositor", |
| 116 "IOThread" : "IO", | 122 "IOThread" : "IO", |
| 117 "CompositorRasterWorker": "raster" | 123 "CompositorRasterWorker": "raster", |
| 124 "DummyThreadName1" : "other", |
| 125 "DummyThreadName2" : "total_fast_path", |
| 126 "DummyThreadName3" : "total_all" |
| 118 } | 127 } |
| 128 |
| 119 MatchBySubString = ["IOThread", "CompositorRasterWorker"] | 129 MatchBySubString = ["IOThread", "CompositorRasterWorker"] |
| 120 FastPath = ["GPU", | 130 FastPath = ["GPU", |
| 121 "browser_main", | 131 "browser_main", |
| 122 "browser_compositor", | 132 "browser_compositor", |
| 123 "renderer_compositor", | 133 "renderer_compositor", |
| 124 "IO"] | 134 "IO"] |
| 125 | 135 |
| 136 AllThreads = TimelineThreadCategories.values() |
| 137 NoThreads = [] |
| 138 MainThread = ["renderer_main"] |
| 139 FastPathResults = AllThreads |
| 140 FastPathDetails = NoThreads |
| 141 SilkResults = ["renderer_main", "total_all"] |
| 142 SilkDetails = MainThread |
| 126 | 143 |
| 127 def ThreadTimePercentageName(category): | 144 def ThreadCategoryName(thread_name): |
| 128 return "thread_" + category + "_clock_time_percentage" | 145 thread_category = "other" |
| 146 for substring, category in TimelineThreadCategories.iteritems(): |
| 147 if substring in MatchBySubString and substring in thread_name: |
| 148 thread_category = category |
| 149 if thread_name in TimelineThreadCategories: |
| 150 thread_category = TimelineThreadCategories[thread_name] |
| 151 return thread_category |
| 129 | 152 |
| 130 def ThreadCPUTimePercentageName(category): | 153 def ThreadTimeResultName(thread_category): |
| 131 return "thread_" + category + "_cpu_time_percentage" | 154 return "thread_" + thread_category + "_clock_time_per_frame" |
| 155 |
| 156 def ThreadCpuTimeResultName(thread_category): |
| 157 return "thread_" + thread_category + "_cpu_time_per_frame" |
| 158 |
| 159 def ThreadDetailResultName(thread_category, detail): |
| 160 return "thread_" + thread_category + "|" + detail |
| 132 | 161 |
| 133 class ResultsForThread(object): | 162 class ResultsForThread(object): |
| 134 def __init__(self, model): | 163 def __init__(self, model, name): |
| 135 self.model = model | 164 self.model = model |
| 136 self.toplevel_slices = [] | 165 self.toplevel_slices = [] |
| 137 self.all_slices = [] | 166 self.all_slices = [] |
| 167 self.name = name |
| 138 | 168 |
| 139 @property | 169 @property |
| 140 def clock_time(self): | 170 def clock_time(self): |
| 141 return sum([x.duration for x in self.toplevel_slices]) | 171 return sum([x.duration for x in self.toplevel_slices]) |
| 142 | 172 |
| 143 @property | 173 @property |
| 144 def cpu_time(self): | 174 def cpu_time(self): |
| 145 res = 0 | 175 res = 0 |
| 146 for x in self.toplevel_slices: | 176 for x in self.toplevel_slices: |
| 147 # Only report thread-duration if we have it for all events. | 177 # Only report thread-duration if we have it for all events. |
| 148 # | 178 # |
| 149 # A thread_duration of 0 is valid, so this only returns 0 if it is None. | 179 # A thread_duration of 0 is valid, so this only returns 0 if it is None. |
| 150 if x.thread_duration == None: | 180 if x.thread_duration == None: |
| 151 return 0 | 181 return 0 |
| 152 else: | 182 else: |
| 153 res += x.thread_duration | 183 res += x.thread_duration |
| 154 return res | 184 return res |
| 155 | 185 |
| 156 def AddDetailedResults(self, thread_category_name, results): | 186 def AppendThreadSlices(self, thread): |
| 187 self.all_slices.extend(thread.all_slices) |
| 188 self.toplevel_slices.extend(thread.toplevel_slices) |
| 189 |
| 190 def AddResults(self, num_frames, results): |
| 191 clock_report_name = ThreadTimeResultName(self.name) |
| 192 cpu_report_name = ThreadCpuTimeResultName(self.name) |
| 193 clock_per_frame = float(self.clock_time) / num_frames |
| 194 cpu_per_frame = float(self.cpu_time) / num_frames |
| 195 results.Add(clock_report_name, 'ms', clock_per_frame) |
| 196 results.Add(cpu_report_name, 'ms', cpu_per_frame) |
| 197 |
| 198 def AddDetailedResults(self, num_frames, results): |
| 157 slices_by_category = collections.defaultdict(list) | 199 slices_by_category = collections.defaultdict(list) |
| 158 for s in self.all_slices: | 200 for s in self.all_slices: |
| 159 slices_by_category[s.category].append(s) | 201 slices_by_category[s.category].append(s) |
| 160 all_self_times = [] | 202 all_self_times = [] |
| 161 for category, slices_in_category in slices_by_category.iteritems(): | 203 for category, slices_in_category in slices_by_category.iteritems(): |
| 162 self_time = sum([x.self_time for x in slices_in_category]) | 204 self_time = sum([x.self_time for x in slices_in_category]) |
| 163 results.Add('%s|%s' % (thread_category_name, category), 'ms', self_time) | |
| 164 all_self_times.append(self_time) | 205 all_self_times.append(self_time) |
| 206 self_time_result = float(self_time) / num_frames |
| 207 results.Add(ThreadDetailResultName(self.name, category), |
| 208 'ms', self_time_result) |
| 165 all_measured_time = sum(all_self_times) | 209 all_measured_time = sum(all_self_times) |
| 166 idle_time = max(0, | 210 idle_time = max(0, self.model.bounds.bounds - all_measured_time) |
| 167 self.model.bounds.bounds - all_measured_time) | 211 idle_time_result = float(idle_time) / num_frames |
| 168 results.Add('%s|idle' % thread_category_name, 'ms', idle_time) | 212 results.Add(ThreadDetailResultName(self.name, "idle"), |
| 213 'ms', idle_time_result) |
| 169 | 214 |
| 170 class ThreadTimesTimelineMetric(TimelineMetric): | 215 class ThreadTimesTimelineMetric(TimelineMetric): |
| 171 def __init__(self): | 216 def __init__(self): |
| 172 super(ThreadTimesTimelineMetric, self).__init__(TRACING_MODE) | 217 super(ThreadTimesTimelineMetric, self).__init__(TRACING_MODE) |
| 173 self.report_renderer_main_details = False | 218 self.results_to_report = AllThreads |
| 219 self.details_to_report = NoThreads |
| 174 | 220 |
| 175 def GetThreadCategoryName(self, thread): | 221 def CalcFrameCount(self): |
| 176 # First determine if we care about this thread. | 222 gpu_swaps = 0 |
| 177 # Check substrings first, followed by exact matches | 223 for thread in self._model.GetAllThreads(): |
| 178 thread_category = None | 224 if (ThreadCategoryName(thread.name) == "GPU"): |
| 179 for substring, category in TimelineThreadCategories.iteritems(): | 225 for event in thread.IterAllSlices(): |
| 180 if substring in thread.name: | 226 if ":RealSwapBuffers" in event.name: |
| 181 thread_category = category | 227 gpu_swaps += 1 |
| 182 if thread.name in TimelineThreadCategories: | 228 return gpu_swaps |
| 183 thread_category = TimelineThreadCategories[thread.name] | |
| 184 if thread_category == None: | |
| 185 thread_category = "other" | |
| 186 | |
| 187 return thread_category | |
| 188 | 229 |
| 189 def AddResults(self, tab, results): | 230 def AddResults(self, tab, results): |
| 190 results_per_thread_category = collections.defaultdict( | 231 num_frames = self.CalcFrameCount() |
| 191 lambda: ResultsForThread(self._model)) | 232 if not num_frames: |
| 233 raise MissingFramesError() |
| 192 | 234 |
| 193 # Set up each category anyway so that we get consistant results. | 235 # Set up each thread category for consistant results. |
| 194 for category in TimelineThreadCategories.values(): | 236 thread_category_results = {} |
| 195 results_per_thread_category[category] = ResultsForThread(self.model) | 237 for name in TimelineThreadCategories.values(): |
| 196 results_for_all_threads = results_per_thread_category['total_fast_path'] | 238 thread_category_results[name] = ResultsForThread(self.model, name) |
| 197 | 239 |
| 198 # Group the slices by their thread category. | 240 # Group the slices by their thread category. |
| 199 for thread in self._model.GetAllThreads(): | 241 for thread in self._model.GetAllThreads(): |
| 200 # First determine if we care about this thread. | 242 thread_category = ThreadCategoryName(thread.name) |
| 201 # Check substrings first, followed by exact matches | 243 thread_category_results[thread_category].AppendThreadSlices(thread) |
| 202 thread_category = self.GetThreadCategoryName(thread) | |
| 203 | 244 |
| 204 results_for_thread = results_per_thread_category[thread_category] | 245 # Group all threads. |
| 205 for event in thread.all_slices: | 246 for thread in self._model.GetAllThreads(): |
| 206 results_for_thread.all_slices.append(event) | 247 thread_category_results['total_all'].AppendThreadSlices(thread) |
| 207 results_for_all_threads.all_slices.append(event) | |
| 208 for event in thread.toplevel_slices: | |
| 209 results_for_thread.toplevel_slices.append(event) | |
| 210 results_for_all_threads.toplevel_slices.append(event) | |
| 211 | 248 |
| 212 for thread_category, results_for_thread_category in ( | 249 # Also group fast-path threads. |
| 213 results_per_thread_category.iteritems()): | 250 for thread in self._model.GetAllThreads(): |
| 214 thread_report_name = ThreadTimePercentageName(thread_category) | 251 if ThreadCategoryName(thread.name) in FastPath: |
| 215 time_as_percentage = (float(results_for_thread_category.clock_time) / | 252 thread_category_results['total_fast_path'].AppendThreadSlices(thread) |
| 216 self._model.bounds.bounds) * 100 | |
| 217 results.Add(thread_report_name, '%', time_as_percentage) | |
| 218 | 253 |
| 219 for thread_category, results_for_thread_category in ( | 254 # Report the desired results and details. |
| 220 results_per_thread_category.iteritems()): | 255 for thread_results in thread_category_results.values(): |
| 221 cpu_time_report_name = ThreadCPUTimePercentageName(thread_category) | 256 if thread_results.name in self.results_to_report: |
| 222 time_as_percentage = (float(results_for_thread_category.cpu_time) / | 257 thread_results.AddResults(num_frames, results) |
| 223 self._model.bounds.bounds) * 100 | 258 # TOOD(nduca): When generic results objects are done, this special case |
| 224 results.Add(cpu_time_report_name, '%', time_as_percentage) | 259 # can be replaced with a generic UI feature. |
| 225 | 260 if thread_results.name in self.details_to_report: |
| 226 # TOOD(nduca): When generic results objects are done, this special case | 261 thread_results.AddDetailedResults(num_frames, results) |
| 227 # can be replaced with a generic UI feature. | |
| 228 for thread_category, results_for_thread_category in ( | |
| 229 results_per_thread_category.iteritems()): | |
| 230 if (thread_category == 'renderer_main' and | |
| 231 self.report_renderer_main_details): | |
| 232 results_for_thread_category.AddDetailedResults(thread_category, results) | |
| OLD | NEW |