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 from telemetry.util.statistics import DivideIfPossibleOrZero |
5 | 6 |
6 from telemetry.web_perf.metrics import timeline_based_metric | 7 from telemetry.web_perf.metrics import timeline_based_metric |
7 from telemetry.value import scalar | 8 from telemetry.value import scalar |
8 | 9 |
9 | 10 |
10 class LoadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric): | 11 class LoadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric): |
11 def __init__(self): | 12 def __init__(self): |
12 super(LoadTimesTimelineMetric, self).__init__() | 13 super(LoadTimesTimelineMetric, self).__init__() |
13 self.report_main_thread_only = True | 14 self.report_main_thread_only = True |
14 | 15 |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
93 ReportMainThreadOnly = ["renderer_main"] | 94 ReportMainThreadOnly = ["renderer_main"] |
94 ReportSilkDetails = ["renderer_main"] | 95 ReportSilkDetails = ["renderer_main"] |
95 | 96 |
96 # 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 |
97 # could change. We should formalize these traces to keep this robust. | 98 # could change. We should formalize these traces to keep this robust. |
98 OverheadTraceCategory = "trace_event_overhead" | 99 OverheadTraceCategory = "trace_event_overhead" |
99 OverheadTraceName = "overhead" | 100 OverheadTraceName = "overhead" |
100 FrameTraceName = "::SwapBuffers" | 101 FrameTraceName = "::SwapBuffers" |
101 FrameTraceThreadName = "renderer_compositor" | 102 FrameTraceThreadName = "renderer_compositor" |
102 | 103 |
| 104 def Rate(numerator, denominator): |
| 105 return DivideIfPossibleOrZero(numerator, denominator) |
103 | 106 |
104 def ClockOverheadForEvent(event): | 107 def ClockOverheadForEvent(event): |
105 if (event.category == OverheadTraceCategory and | 108 if (event.category == OverheadTraceCategory and |
106 event.name == OverheadTraceName): | 109 event.name == OverheadTraceName): |
107 return event.duration | 110 return event.duration |
108 else: | 111 else: |
109 return 0 | 112 return 0 |
110 | 113 |
111 def CpuOverheadForEvent(event): | 114 def CpuOverheadForEvent(event): |
112 if (event.category == OverheadTraceCategory and | 115 if (event.category == OverheadTraceCategory and |
113 event.thread_duration): | 116 event.thread_duration): |
114 return event.thread_duration | 117 return event.thread_duration |
115 else: | 118 else: |
116 return 0 | 119 return 0 |
117 | 120 |
118 def ThreadCategoryName(thread_name): | 121 def ThreadCategoryName(thread_name): |
119 thread_category = "other" | 122 thread_category = "other" |
120 for substring, category in TimelineThreadCategories.iteritems(): | 123 for substring, category in TimelineThreadCategories.iteritems(): |
121 if substring in _MatchBySubString and substring in thread_name: | 124 if substring in _MatchBySubString and substring in thread_name: |
122 thread_category = category | 125 thread_category = category |
123 if thread_name in TimelineThreadCategories: | 126 if thread_name in TimelineThreadCategories: |
124 thread_category = TimelineThreadCategories[thread_name] | 127 thread_category = TimelineThreadCategories[thread_name] |
125 return thread_category | 128 return thread_category |
126 | 129 |
127 def ThreadTimeResultName(thread_category): | 130 def ThreadCpuTimeResultName(thread_category): |
128 return "thread_" + thread_category + "_clock_time_per_frame" | 131 # 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" |
129 | 133 |
130 def ThreadCpuTimeResultName(thread_category): | 134 def ThreadTasksResultName(thread_category): |
131 return "thread_" + thread_category + "_cpu_time_per_frame" | 135 return "tasks_per_frame_" + thread_category |
| 136 |
| 137 def ThreadMeanFrameTimeResultName(thread_category): |
| 138 return "mean_frame_time_" + thread_category |
132 | 139 |
133 def ThreadDetailResultName(thread_category, detail): | 140 def ThreadDetailResultName(thread_category, detail): |
134 detail_sanitized = detail.replace('.','_') | 141 detail_sanitized = detail.replace('.','_') |
135 return "thread_" + thread_category + "|" + detail_sanitized | 142 return "thread_" + thread_category + "|" + detail_sanitized |
136 | 143 |
137 | 144 |
138 class ResultsForThread(object): | 145 class ResultsForThread(object): |
139 def __init__(self, model, record_ranges, name): | 146 def __init__(self, model, record_ranges, name): |
140 self.model = model | 147 self.model = model |
141 self.toplevel_slices = [] | 148 self.toplevel_slices = [] |
142 self.all_slices = [] | 149 self.all_slices = [] |
143 self.name = name | 150 self.name = name |
144 self.record_ranges = record_ranges | 151 self.record_ranges = record_ranges |
| 152 self.all_action_time = \ |
| 153 sum([record_range.bounds for record_range in self.record_ranges]) |
145 | 154 |
146 @property | 155 @property |
147 def clock_time(self): | 156 def clock_time(self): |
148 clock_duration = sum([x.duration for x in self.toplevel_slices]) | 157 clock_duration = sum([x.duration for x in self.toplevel_slices]) |
149 clock_overhead = sum([ClockOverheadForEvent(x) for x in self.all_slices]) | 158 clock_overhead = sum([ClockOverheadForEvent(x) for x in self.all_slices]) |
150 return clock_duration - clock_overhead | 159 return clock_duration - clock_overhead |
151 | 160 |
152 @property | 161 @property |
153 def cpu_time(self): | 162 def cpu_time(self): |
154 cpu_duration = 0 | 163 cpu_duration = 0 |
(...skipping 17 matching lines...) Expand all Loading... |
172 for record_range in self.record_ranges: | 181 for record_range in self.record_ranges: |
173 if record_range.ContainsInterval(event.start, event.end): | 182 if record_range.ContainsInterval(event.start, event.end): |
174 slices_in_actions.append(event) | 183 slices_in_actions.append(event) |
175 break | 184 break |
176 return slices_in_actions | 185 return slices_in_actions |
177 | 186 |
178 def AppendThreadSlices(self, thread): | 187 def AppendThreadSlices(self, thread): |
179 self.all_slices.extend(self.SlicesInActions(thread.all_slices)) | 188 self.all_slices.extend(self.SlicesInActions(thread.all_slices)) |
180 self.toplevel_slices.extend(self.SlicesInActions(thread.toplevel_slices)) | 189 self.toplevel_slices.extend(self.SlicesInActions(thread.toplevel_slices)) |
181 | 190 |
| 191 # Currently we report cpu-time per frame, tasks per frame, and possibly |
| 192 # the mean frame (if there is a trace specified to find it). |
182 def AddResults(self, num_frames, results): | 193 def AddResults(self, num_frames, results): |
183 cpu_per_frame = (float(self.cpu_time) / num_frames) if num_frames else 0 | 194 cpu_per_frame = Rate(self.cpu_time, num_frames) |
| 195 tasks_per_frame = Rate(len(self.toplevel_slices), num_frames) |
184 results.AddValue(scalar.ScalarValue( | 196 results.AddValue(scalar.ScalarValue( |
185 results.current_page, ThreadCpuTimeResultName(self.name), | 197 results.current_page, ThreadCpuTimeResultName(self.name), |
186 'ms', cpu_per_frame)) | 198 'ms', cpu_per_frame)) |
| 199 results.AddValue(scalar.ScalarValue( |
| 200 results.current_page, ThreadTasksResultName(self.name), |
| 201 'tasks', tasks_per_frame)) |
| 202 # Report mean frame time if this is the thread we are using for normalizing |
| 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)) |
187 | 211 |
188 def AddDetailedResults(self, num_frames, results): | 212 def AddDetailedResults(self, num_frames, results): |
189 slices_by_category = collections.defaultdict(list) | 213 slices_by_category = collections.defaultdict(list) |
190 for s in self.all_slices: | 214 for s in self.all_slices: |
191 slices_by_category[s.category].append(s) | 215 slices_by_category[s.category].append(s) |
192 all_self_times = [] | 216 all_self_times = [] |
193 for category, slices_in_category in slices_by_category.iteritems(): | 217 for category, slices_in_category in slices_by_category.iteritems(): |
194 self_time = sum([x.self_time for x in slices_in_category]) | 218 self_time = sum([x.self_time for x in slices_in_category]) |
195 all_self_times.append(self_time) | 219 all_self_times.append(self_time) |
196 self_time_result = (float(self_time) / num_frames) if num_frames else 0 | 220 self_time_result = (float(self_time) / num_frames) if num_frames else 0 |
197 results.AddValue(scalar.ScalarValue( | 221 results.AddValue(scalar.ScalarValue( |
198 results.current_page, ThreadDetailResultName(self.name, category), | 222 results.current_page, ThreadDetailResultName(self.name, category), |
199 'ms', self_time_result)) | 223 'ms', self_time_result)) |
200 all_measured_time = sum(all_self_times) | 224 all_measured_time = sum(all_self_times) |
201 all_action_time = \ | 225 idle_time = max(0, self.all_action_time - all_measured_time) |
202 sum([record_range.bounds for record_range in self.record_ranges]) | |
203 idle_time = max(0, all_action_time - all_measured_time) | |
204 idle_time_result = (float(idle_time) / num_frames) if num_frames else 0 | 226 idle_time_result = (float(idle_time) / num_frames) if num_frames else 0 |
205 results.AddValue(scalar.ScalarValue( | 227 results.AddValue(scalar.ScalarValue( |
206 results.current_page, ThreadDetailResultName(self.name, "idle"), | 228 results.current_page, ThreadDetailResultName(self.name, "idle"), |
207 'ms', idle_time_result)) | 229 'ms', idle_time_result)) |
208 | 230 |
| 231 def CountTracesWithName(self, substring): |
| 232 count = 0 |
| 233 for event in self.all_slices: |
| 234 if substring in event.name: |
| 235 count += 1 |
| 236 return count |
209 | 237 |
210 class ThreadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric): | 238 class ThreadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric): |
211 def __init__(self): | 239 def __init__(self): |
212 super(ThreadTimesTimelineMetric, self).__init__() | 240 super(ThreadTimesTimelineMetric, self).__init__() |
213 # Minimal traces, for minimum noise in CPU-time measurements. | 241 # Minimal traces, for minimum noise in CPU-time measurements. |
214 self.results_to_report = AllThreads | 242 self.results_to_report = AllThreads |
215 self.details_to_report = NoThreads | 243 self.details_to_report = NoThreads |
216 | 244 |
217 def CountSlices(self, slices, substring): | |
218 count = 0 | |
219 for event in slices: | |
220 if substring in event.name: | |
221 count += 1 | |
222 return count | |
223 | |
224 def AddResults(self, model, _, interaction_records, results): | 245 def AddResults(self, model, _, interaction_records, results): |
225 # Set up each thread category for consistant results. | 246 # Set up each thread category for consistant results. |
226 thread_category_results = {} | 247 thread_category_results = {} |
227 for name in TimelineThreadCategories.values(): | 248 for name in TimelineThreadCategories.values(): |
228 thread_category_results[name] = ResultsForThread( | 249 thread_category_results[name] = ResultsForThread( |
229 model, [r.GetBounds() for r in interaction_records], name) | 250 model, [r.GetBounds() for r in interaction_records], name) |
230 | 251 |
231 # Group the slices by their thread category. | 252 # Group the slices by their thread category. |
232 for thread in model.GetAllThreads(): | 253 for thread in model.GetAllThreads(): |
233 thread_category = ThreadCategoryName(thread.name) | 254 thread_category = ThreadCategoryName(thread.name) |
234 thread_category_results[thread_category].AppendThreadSlices(thread) | 255 thread_category_results[thread_category].AppendThreadSlices(thread) |
235 | 256 |
236 # Group all threads. | 257 # Group all threads. |
237 for thread in model.GetAllThreads(): | 258 for thread in model.GetAllThreads(): |
238 thread_category_results['total_all'].AppendThreadSlices(thread) | 259 thread_category_results['total_all'].AppendThreadSlices(thread) |
239 | 260 |
240 # Also group fast-path threads. | 261 # Also group fast-path threads. |
241 for thread in model.GetAllThreads(): | 262 for thread in model.GetAllThreads(): |
242 if ThreadCategoryName(thread.name) in FastPathThreads: | 263 if ThreadCategoryName(thread.name) in FastPathThreads: |
243 thread_category_results['total_fast_path'].AppendThreadSlices(thread) | 264 thread_category_results['total_fast_path'].AppendThreadSlices(thread) |
244 | 265 |
245 # Calculate the number of frames. | 266 # Calculate the number of frames. |
246 frame_slices = thread_category_results[FrameTraceThreadName].all_slices | 267 frame_rate_thread = thread_category_results[FrameTraceThreadName] |
247 num_frames = self.CountSlices(frame_slices, FrameTraceName) | 268 num_frames = frame_rate_thread.CountTracesWithName(FrameTraceName) |
248 | 269 |
249 # Report the desired results and details. | 270 # Report the desired results and details. |
250 for thread_results in thread_category_results.values(): | 271 for thread_results in thread_category_results.values(): |
251 if thread_results.name in self.results_to_report: | 272 if thread_results.name in self.results_to_report: |
252 thread_results.AddResults(num_frames, results) | 273 thread_results.AddResults(num_frames, results) |
253 # TOOD(nduca): When generic results objects are done, this special case | 274 # TOOD(nduca): When generic results objects are done, this special case |
254 # can be replaced with a generic UI feature. | 275 # can be replaced with a generic UI feature. |
255 if thread_results.name in self.details_to_report: | 276 if thread_results.name in self.details_to_report: |
256 thread_results.AddDetailedResults(num_frames, results) | 277 thread_results.AddDetailedResults(num_frames, results) |
OLD | NEW |