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 |