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 telemetry.util.statistics import DivideIfPossibleOrZero | 6 from telemetry.util.statistics import DivideIfPossibleOrZero |
7 from telemetry.value import scalar | 7 from telemetry.value import scalar |
8 from telemetry.web_perf.metrics import timeline_based_metric | 8 from telemetry.web_perf.metrics import timeline_based_metric |
9 | 9 |
10 | 10 |
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
94 ReportMainThreadOnly = ["renderer_main"] | 94 ReportMainThreadOnly = ["renderer_main"] |
95 ReportSilkDetails = ["renderer_main"] | 95 ReportSilkDetails = ["renderer_main"] |
96 | 96 |
97 # TODO(epenner): Thread names above are likely fairly stable but trace names | 97 # TODO(epenner): Thread names above are likely fairly stable but trace names |
98 # could change. We should formalize these traces to keep this robust. | 98 # could change. We should formalize these traces to keep this robust. |
99 OverheadTraceCategory = "trace_event_overhead" | 99 OverheadTraceCategory = "trace_event_overhead" |
100 OverheadTraceName = "overhead" | 100 OverheadTraceName = "overhead" |
101 FrameTraceName = "::SwapBuffers" | 101 FrameTraceName = "::SwapBuffers" |
102 FrameTraceThreadName = "renderer_compositor" | 102 FrameTraceThreadName = "renderer_compositor" |
103 | 103 |
104 IntervalNames = ["frame", "ms"] | |
105 | |
104 def Rate(numerator, denominator): | 106 def Rate(numerator, denominator): |
105 return DivideIfPossibleOrZero(numerator, denominator) | 107 return DivideIfPossibleOrZero(numerator, denominator) |
106 | 108 |
107 def ClockOverheadForEvent(event): | 109 def ClockOverheadForEvent(event): |
108 if (event.category == OverheadTraceCategory and | 110 if (event.category == OverheadTraceCategory and |
109 event.name == OverheadTraceName): | 111 event.name == OverheadTraceName): |
110 return event.duration | 112 return event.duration |
111 else: | 113 else: |
112 return 0 | 114 return 0 |
113 | 115 |
114 def CpuOverheadForEvent(event): | 116 def CpuOverheadForEvent(event): |
115 if (event.category == OverheadTraceCategory and | 117 if (event.category == OverheadTraceCategory and |
116 event.thread_duration): | 118 event.thread_duration): |
117 return event.thread_duration | 119 return event.thread_duration |
118 else: | 120 else: |
119 return 0 | 121 return 0 |
120 | 122 |
121 def ThreadCategoryName(thread_name): | 123 def ThreadCategoryName(thread_name): |
122 thread_category = "other" | 124 thread_category = "other" |
123 for substring, category in TimelineThreadCategories.iteritems(): | 125 for substring, category in TimelineThreadCategories.iteritems(): |
124 if substring in _MatchBySubString and substring in thread_name: | 126 if substring in _MatchBySubString and substring in thread_name: |
125 thread_category = category | 127 thread_category = category |
126 if thread_name in TimelineThreadCategories: | 128 if thread_name in TimelineThreadCategories: |
127 thread_category = TimelineThreadCategories[thread_name] | 129 thread_category = TimelineThreadCategories[thread_name] |
128 return thread_category | 130 return thread_category |
129 | 131 |
130 def ThreadCpuTimeResultName(thread_category): | 132 def ThreadCpuTimeResultName(thread_category, interval_name): |
131 # This isn't a good name, but I don't want to change it and lose continuity. | 133 # This isn't a good name, but I don't want to change it and lose continuity. |
132 return "thread_" + thread_category + "_cpu_time_per_frame" | 134 return "thread_" + thread_category + "_cpu_time_per_" + interval_name |
133 | 135 |
134 def ThreadTasksResultName(thread_category): | 136 def ThreadTasksResultName(thread_category, interval_name): |
135 return "tasks_per_frame_" + thread_category | 137 return "tasks_per_" + interval_name + "_" + thread_category |
136 | 138 |
137 def ThreadMeanFrameTimeResultName(thread_category): | 139 def ThreadMeanFrameTimeResultName(thread_category): |
138 return "mean_frame_time_" + thread_category | 140 return "mean_frame_time_" + thread_category |
139 | 141 |
140 def ThreadDetailResultName(thread_category, detail): | 142 def ThreadDetailResultName(thread_category, interval_name, detail): |
141 detail_sanitized = detail.replace('.','_') | 143 detail_sanitized = detail.replace('.','_') |
142 return "thread_" + thread_category + "|" + detail_sanitized | 144 interval_sanitized = "" |
145 # Special-case per-frame detail names to preserve continuity. | |
146 if interval_name == "frame": | |
147 interval_sanitized = "" | |
148 else: | |
149 interval_sanitized = "_per_" + interval_name | |
150 return ( | |
151 "thread_" + thread_category + interval_sanitized + "|" + detail_sanitized) | |
143 | 152 |
144 | 153 |
145 class ResultsForThread(object): | 154 class ResultsForThread(object): |
146 def __init__(self, model, record_ranges, name): | 155 def __init__(self, model, record_ranges, name): |
147 self.model = model | 156 self.model = model |
148 self.toplevel_slices = [] | 157 self.toplevel_slices = [] |
149 self.all_slices = [] | 158 self.all_slices = [] |
150 self.name = name | 159 self.name = name |
151 self.record_ranges = record_ranges | 160 self.record_ranges = record_ranges |
152 self.all_action_time = \ | 161 self.all_action_time = \ |
(...skipping 28 matching lines...) Expand all Loading... | |
181 for record_range in self.record_ranges: | 190 for record_range in self.record_ranges: |
182 if record_range.ContainsInterval(event.start, event.end): | 191 if record_range.ContainsInterval(event.start, event.end): |
183 slices_in_actions.append(event) | 192 slices_in_actions.append(event) |
184 break | 193 break |
185 return slices_in_actions | 194 return slices_in_actions |
186 | 195 |
187 def AppendThreadSlices(self, thread): | 196 def AppendThreadSlices(self, thread): |
188 self.all_slices.extend(self.SlicesInActions(thread.all_slices)) | 197 self.all_slices.extend(self.SlicesInActions(thread.all_slices)) |
189 self.toplevel_slices.extend(self.SlicesInActions(thread.toplevel_slices)) | 198 self.toplevel_slices.extend(self.SlicesInActions(thread.toplevel_slices)) |
190 | 199 |
191 # Currently we report cpu-time per frame, tasks per frame, and possibly | 200 # Reports cpu-time per interval and tasks per interval. |
192 # the mean frame (if there is a trace specified to find it). | 201 def AddResults(self, num_intervals, interval_name, results): |
193 def AddResults(self, num_frames, results): | 202 cpu_per_interval = Rate(self.cpu_time, num_intervals) |
194 cpu_per_frame = Rate(self.cpu_time, num_frames) | 203 tasks_per_interval = Rate(len(self.toplevel_slices), num_intervals) |
195 tasks_per_frame = Rate(len(self.toplevel_slices), num_frames) | |
196 results.AddValue(scalar.ScalarValue( | 204 results.AddValue(scalar.ScalarValue( |
197 results.current_page, ThreadCpuTimeResultName(self.name), | 205 results.current_page, |
198 'ms', cpu_per_frame)) | 206 ThreadCpuTimeResultName(self.name, interval_name), |
207 'ms', cpu_per_interval)) | |
nednguyen
2015/05/04 00:39:46
Update unit here and other places?
| |
199 results.AddValue(scalar.ScalarValue( | 208 results.AddValue(scalar.ScalarValue( |
200 results.current_page, ThreadTasksResultName(self.name), | 209 results.current_page, |
201 'tasks', tasks_per_frame)) | 210 ThreadTasksResultName(self.name, interval_name), |
202 # Report mean frame time if this is the thread we are using for normalizing | 211 'tasks', tasks_per_interval)) |
203 # other results. We could report other frame rates (eg. renderer_main) but | |
204 # this might get confusing. | |
205 if self.name == FrameTraceThreadName: | |
206 num_frames = self.CountTracesWithName(FrameTraceName) | |
207 mean_frame_time = Rate(self.all_action_time, num_frames) | |
208 results.AddValue(scalar.ScalarValue( | |
209 results.current_page, ThreadMeanFrameTimeResultName(self.name), | |
210 'ms', mean_frame_time)) | |
211 | 212 |
212 def AddDetailedResults(self, num_frames, results): | 213 def AddDetailedResults(self, num_intervals, interval_name, results): |
213 slices_by_category = collections.defaultdict(list) | 214 slices_by_category = collections.defaultdict(list) |
214 for s in self.all_slices: | 215 for s in self.all_slices: |
215 slices_by_category[s.category].append(s) | 216 slices_by_category[s.category].append(s) |
216 all_self_times = [] | 217 all_self_times = [] |
217 for category, slices_in_category in slices_by_category.iteritems(): | 218 for category, slices_in_category in slices_by_category.iteritems(): |
218 self_time = sum([x.self_time for x in slices_in_category]) | 219 self_time = sum([x.self_time for x in slices_in_category]) |
219 all_self_times.append(self_time) | 220 all_self_times.append(self_time) |
220 self_time_result = (float(self_time) / num_frames) if num_frames else 0 | 221 self_time_result = ( |
222 (float(self_time) / num_intervals) if num_intervals else 0) | |
221 results.AddValue(scalar.ScalarValue( | 223 results.AddValue(scalar.ScalarValue( |
222 results.current_page, ThreadDetailResultName(self.name, category), | 224 results.current_page, |
225 ThreadDetailResultName(self.name, interval_name, category), | |
223 'ms', self_time_result)) | 226 'ms', self_time_result)) |
224 all_measured_time = sum(all_self_times) | 227 all_measured_time = sum(all_self_times) |
225 idle_time = max(0, self.all_action_time - all_measured_time) | 228 idle_time = max(0, self.all_action_time - all_measured_time) |
226 idle_time_result = (float(idle_time) / num_frames) if num_frames else 0 | 229 idle_time_result = ( |
230 (float(idle_time) / num_intervals) if num_intervals else 0) | |
227 results.AddValue(scalar.ScalarValue( | 231 results.AddValue(scalar.ScalarValue( |
228 results.current_page, ThreadDetailResultName(self.name, "idle"), | 232 results.current_page, |
233 ThreadDetailResultName(self.name, interval_name, "idle"), | |
229 'ms', idle_time_result)) | 234 'ms', idle_time_result)) |
230 | 235 |
231 def CountTracesWithName(self, substring): | 236 def CountTracesWithName(self, substring): |
232 count = 0 | 237 count = 0 |
233 for event in self.all_slices: | 238 for event in self.all_slices: |
234 if substring in event.name: | 239 if substring in event.name: |
235 count += 1 | 240 count += 1 |
236 return count | 241 return count |
237 | 242 |
243 | |
238 class ThreadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric): | 244 class ThreadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric): |
239 def __init__(self): | 245 def __init__(self): |
240 super(ThreadTimesTimelineMetric, self).__init__() | 246 super(ThreadTimesTimelineMetric, self).__init__() |
241 # Minimal traces, for minimum noise in CPU-time measurements. | 247 # Minimal traces, for minimum noise in CPU-time measurements. |
242 self.results_to_report = AllThreads | 248 self.results_to_report = AllThreads |
243 self.details_to_report = NoThreads | 249 self.details_to_report = NoThreads |
244 | 250 |
245 def AddResults(self, model, _, interaction_records, results): | 251 def AddResults(self, model, _, interaction_records, results): |
246 # Set up each thread category for consistant results. | 252 # Set up each thread category for consistant results. |
247 thread_category_results = {} | 253 thread_category_results = {} |
248 for name in TimelineThreadCategories.values(): | 254 for name in TimelineThreadCategories.values(): |
249 thread_category_results[name] = ResultsForThread( | 255 thread_category_results[name] = ResultsForThread( |
250 model, [r.GetBounds() for r in interaction_records], name) | 256 model, [r.GetBounds() for r in interaction_records], name) |
251 | 257 |
252 # Group the slices by their thread category. | 258 # Group the slices by their thread category. |
253 for thread in model.GetAllThreads(): | 259 for thread in model.GetAllThreads(): |
254 thread_category = ThreadCategoryName(thread.name) | 260 thread_category = ThreadCategoryName(thread.name) |
255 thread_category_results[thread_category].AppendThreadSlices(thread) | 261 thread_category_results[thread_category].AppendThreadSlices(thread) |
256 | 262 |
257 # Group all threads. | 263 # Group all threads. |
258 for thread in model.GetAllThreads(): | 264 for thread in model.GetAllThreads(): |
259 thread_category_results['total_all'].AppendThreadSlices(thread) | 265 thread_category_results['total_all'].AppendThreadSlices(thread) |
260 | 266 |
261 # Also group fast-path threads. | 267 # Also group fast-path threads. |
262 for thread in model.GetAllThreads(): | 268 for thread in model.GetAllThreads(): |
263 if ThreadCategoryName(thread.name) in FastPathThreads: | 269 if ThreadCategoryName(thread.name) in FastPathThreads: |
264 thread_category_results['total_fast_path'].AppendThreadSlices(thread) | 270 thread_category_results['total_fast_path'].AppendThreadSlices(thread) |
265 | 271 |
266 # Calculate the number of frames. | 272 # Calculate the interaction's number of frames. |
267 frame_rate_thread = thread_category_results[FrameTraceThreadName] | 273 frame_rate_thread = thread_category_results[FrameTraceThreadName] |
268 num_frames = frame_rate_thread.CountTracesWithName(FrameTraceName) | 274 num_frames = frame_rate_thread.CountTracesWithName(FrameTraceName) |
269 | 275 |
270 # Report the desired results and details. | 276 # Calculate the interaction's duration. |
271 for thread_results in thread_category_results.values(): | 277 all_threads = thread_category_results['total_all'] |
272 if thread_results.name in self.results_to_report: | 278 num_ms = all_threads.all_action_time |
273 thread_results.AddResults(num_frames, results) | 279 |
274 # TOOD(nduca): When generic results objects are done, this special case | 280 # Report the desired results and details for each interval type. |
275 # can be replaced with a generic UI feature. | 281 intervals = [("frame", num_frames), ("ms", num_ms)] |
276 if thread_results.name in self.details_to_report: | 282 for (interval_name, num_intervals) in intervals: |
277 thread_results.AddDetailedResults(num_frames, results) | 283 for thread_results in thread_category_results.values(): |
284 if thread_results.name in self.results_to_report: | |
285 thread_results.AddResults(num_intervals, interval_name, results) | |
286 # TOOD(nduca): When generic results objects are done, this special case | |
287 # can be replaced with a generic UI feature. | |
288 if thread_results.name in self.details_to_report: | |
289 thread_results.AddDetailedResults( | |
290 num_intervals, interval_name, results) | |
291 | |
292 # Report mean frame time for the frame rate thread. We could report other | |
293 # frame rates (eg. renderer_main) but this might get confusing. | |
294 mean_frame_time = Rate(frame_rate_thread.all_action_time, num_frames) | |
295 results.AddValue(scalar.ScalarValue( | |
296 results.current_page, | |
297 ThreadMeanFrameTimeResultName(FrameTraceThreadName), | |
298 'ms', mean_frame_time)) | |
OLD | NEW |