Chromium Code Reviews| 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", "second"] | |
| 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 ThreadMeanTimeResultName(thread_category, interval_name): |
| 138 return "mean_frame_time_" + thread_category | 140 return "mean_" + interval_name + "_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 # Special-case per-frame detail names to preserve continuity. |
| 145 interval_sanitized = ( | |
| 146 "" if interval_name == "frame" else "_per_" + interval_name) | |
| 147 return ( | |
| 148 "thread_" + thread_category + interval_sanitized + "|" + detail_sanitized) | |
| 143 | 149 |
| 144 | 150 |
| 145 class ResultsForThread(object): | 151 class ResultsForThread(object): |
| 146 def __init__(self, model, record_ranges, name): | 152 def __init__(self, model, record_ranges, name): |
| 147 self.model = model | 153 self.model = model |
| 148 self.toplevel_slices = [] | 154 self.toplevel_slices = [] |
| 149 self.all_slices = [] | 155 self.all_slices = [] |
| 150 self.name = name | 156 self.name = name |
| 151 self.record_ranges = record_ranges | 157 self.record_ranges = record_ranges |
| 152 self.all_action_time = \ | 158 self.all_action_time = \ |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 181 for record_range in self.record_ranges: | 187 for record_range in self.record_ranges: |
| 182 if record_range.ContainsInterval(event.start, event.end): | 188 if record_range.ContainsInterval(event.start, event.end): |
| 183 slices_in_actions.append(event) | 189 slices_in_actions.append(event) |
| 184 break | 190 break |
| 185 return slices_in_actions | 191 return slices_in_actions |
| 186 | 192 |
| 187 def AppendThreadSlices(self, thread): | 193 def AppendThreadSlices(self, thread): |
| 188 self.all_slices.extend(self.SlicesInActions(thread.all_slices)) | 194 self.all_slices.extend(self.SlicesInActions(thread.all_slices)) |
| 189 self.toplevel_slices.extend(self.SlicesInActions(thread.toplevel_slices)) | 195 self.toplevel_slices.extend(self.SlicesInActions(thread.toplevel_slices)) |
| 190 | 196 |
| 191 # Currently we report cpu-time per frame, tasks per frame, and possibly | 197 # Reports cpu-time per interval and tasks per interval. |
| 192 # the mean frame (if there is a trace specified to find it). | 198 def AddResults(self, num_intervals, interval_name, results): |
| 193 def AddResults(self, num_frames, results): | 199 cpu_per_interval = Rate(self.cpu_time, num_intervals) |
| 194 cpu_per_frame = Rate(self.cpu_time, num_frames) | 200 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( | 201 results.AddValue(scalar.ScalarValue( |
| 197 results.current_page, ThreadCpuTimeResultName(self.name), | 202 results.current_page, |
| 198 'ms', cpu_per_frame)) | 203 ThreadCpuTimeResultName(self.name, interval_name), |
| 204 'ms', cpu_per_interval)) | |
| 199 results.AddValue(scalar.ScalarValue( | 205 results.AddValue(scalar.ScalarValue( |
| 200 results.current_page, ThreadTasksResultName(self.name), | 206 results.current_page, |
| 201 'tasks', tasks_per_frame)) | 207 ThreadTasksResultName(self.name, interval_name), |
| 208 'tasks', tasks_per_interval)) | |
| 202 # Report mean frame time if this is the thread we are using for normalizing | 209 # 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 | 210 # other results. We could report other frame rates (eg. renderer_main) but |
| 204 # this might get confusing. | 211 # this might get confusing. |
| 205 if self.name == FrameTraceThreadName: | 212 if self.name == FrameTraceThreadName: |
| 206 num_frames = self.CountTracesWithName(FrameTraceName) | 213 num_frames = self.CountTracesWithName(FrameTraceName) |
| 207 mean_frame_time = Rate(self.all_action_time, num_frames) | 214 mean_frame_time = Rate(self.all_action_time, num_frames) |
| 208 results.AddValue(scalar.ScalarValue( | 215 results.AddValue(scalar.ScalarValue( |
| 209 results.current_page, ThreadMeanFrameTimeResultName(self.name), | 216 results.current_page, |
| 217 ThreadMeanTimeResultName(self.name, interval_name), | |
| 210 'ms', mean_frame_time)) | 218 'ms', mean_frame_time)) |
| 211 | 219 |
| 212 def AddDetailedResults(self, num_frames, results): | 220 def AddDetailedResults(self, num_intervals, interval_name, results): |
| 213 slices_by_category = collections.defaultdict(list) | 221 slices_by_category = collections.defaultdict(list) |
| 214 for s in self.all_slices: | 222 for s in self.all_slices: |
| 215 slices_by_category[s.category].append(s) | 223 slices_by_category[s.category].append(s) |
| 216 all_self_times = [] | 224 all_self_times = [] |
| 217 for category, slices_in_category in slices_by_category.iteritems(): | 225 for category, slices_in_category in slices_by_category.iteritems(): |
| 218 self_time = sum([x.self_time for x in slices_in_category]) | 226 self_time = sum([x.self_time for x in slices_in_category]) |
| 219 all_self_times.append(self_time) | 227 all_self_times.append(self_time) |
| 220 self_time_result = (float(self_time) / num_frames) if num_frames else 0 | 228 self_time_result = ( |
| 229 (float(self_time) / num_intervals) if num_intervals else 0) | |
| 221 results.AddValue(scalar.ScalarValue( | 230 results.AddValue(scalar.ScalarValue( |
| 222 results.current_page, ThreadDetailResultName(self.name, category), | 231 results.current_page, |
| 232 ThreadDetailResultName(self.name, interval_name, category), | |
| 223 'ms', self_time_result)) | 233 'ms', self_time_result)) |
| 224 all_measured_time = sum(all_self_times) | 234 all_measured_time = sum(all_self_times) |
| 225 idle_time = max(0, self.all_action_time - all_measured_time) | 235 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 | 236 idle_time_result = ( |
| 237 (float(idle_time) / num_intervals) if num_intervals else 0) | |
| 227 results.AddValue(scalar.ScalarValue( | 238 results.AddValue(scalar.ScalarValue( |
| 228 results.current_page, ThreadDetailResultName(self.name, "idle"), | 239 results.current_page, |
| 240 ThreadDetailResultName(self.name, interval_name, "idle"), | |
| 229 'ms', idle_time_result)) | 241 'ms', idle_time_result)) |
| 230 | 242 |
| 231 def CountTracesWithName(self, substring): | 243 def CountTracesWithName(self, substring): |
| 232 count = 0 | 244 count = 0 |
| 233 for event in self.all_slices: | 245 for event in self.all_slices: |
| 234 if substring in event.name: | 246 if substring in event.name: |
| 235 count += 1 | 247 count += 1 |
| 236 return count | 248 return count |
| 237 | 249 |
| 250 def ComputeSliceTimeRange(self): | |
| 251 if not self.all_slices: | |
| 252 return 0 | |
| 253 | |
| 254 start = min(s.start for s in self.all_slices) | |
| 255 end = max(s.end for s in self.all_slices) | |
| 256 return max(0, end - start) | |
| 257 | |
| 258 | |
| 238 class ThreadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric): | 259 class ThreadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric): |
| 239 def __init__(self): | 260 def __init__(self): |
| 240 super(ThreadTimesTimelineMetric, self).__init__() | 261 super(ThreadTimesTimelineMetric, self).__init__() |
| 241 # Minimal traces, for minimum noise in CPU-time measurements. | 262 # Minimal traces, for minimum noise in CPU-time measurements. |
| 242 self.results_to_report = AllThreads | 263 self.results_to_report = AllThreads |
| 243 self.details_to_report = NoThreads | 264 self.details_to_report = NoThreads |
| 244 | 265 |
| 245 def AddResults(self, model, _, interaction_records, results): | 266 def AddResults(self, model, _, interaction_records, results): |
| 246 # Set up each thread category for consistant results. | 267 # Set up each thread category for consistant results. |
| 247 thread_category_results = {} | 268 thread_category_results = {} |
| 248 for name in TimelineThreadCategories.values(): | 269 for name in TimelineThreadCategories.values(): |
| 249 thread_category_results[name] = ResultsForThread( | 270 thread_category_results[name] = ResultsForThread( |
| 250 model, [r.GetBounds() for r in interaction_records], name) | 271 model, [r.GetBounds() for r in interaction_records], name) |
| 251 | 272 |
| 252 # Group the slices by their thread category. | 273 # Group the slices by their thread category. |
| 253 for thread in model.GetAllThreads(): | 274 for thread in model.GetAllThreads(): |
| 254 thread_category = ThreadCategoryName(thread.name) | 275 thread_category = ThreadCategoryName(thread.name) |
| 255 thread_category_results[thread_category].AppendThreadSlices(thread) | 276 thread_category_results[thread_category].AppendThreadSlices(thread) |
| 256 | 277 |
| 257 # Group all threads. | 278 # Group all threads. |
| 258 for thread in model.GetAllThreads(): | 279 for thread in model.GetAllThreads(): |
| 259 thread_category_results['total_all'].AppendThreadSlices(thread) | 280 thread_category_results['total_all'].AppendThreadSlices(thread) |
| 260 | 281 |
| 261 # Also group fast-path threads. | 282 # Also group fast-path threads. |
| 262 for thread in model.GetAllThreads(): | 283 for thread in model.GetAllThreads(): |
| 263 if ThreadCategoryName(thread.name) in FastPathThreads: | 284 if ThreadCategoryName(thread.name) in FastPathThreads: |
| 264 thread_category_results['total_fast_path'].AppendThreadSlices(thread) | 285 thread_category_results['total_fast_path'].AppendThreadSlices(thread) |
| 265 | 286 |
| 266 # Calculate the number of frames. | 287 # Calculate the interaction's number of frames. |
| 267 frame_rate_thread = thread_category_results[FrameTraceThreadName] | 288 frame_rate_thread = thread_category_results[FrameTraceThreadName] |
| 268 num_frames = frame_rate_thread.CountTracesWithName(FrameTraceName) | 289 num_frames = frame_rate_thread.CountTracesWithName(FrameTraceName) |
| 269 | 290 |
| 270 # Report the desired results and details. | 291 # Calculate the interaction's duration. |
| 271 for thread_results in thread_category_results.values(): | 292 all_threads = thread_category_results['total_all'] |
| 272 if thread_results.name in self.results_to_report: | 293 num_seconds = all_threads.ComputeSliceTimeRange() / 1000.0 |
|
nednguyen
2015/05/01 19:15:10
Don't you want to use num_seconds = sum all the ti
jdduke (slow)
2015/05/01 21:08:47
I see now, so we can just reuse ResultsForThread.a
| |
| 273 thread_results.AddResults(num_frames, results) | 294 |
| 274 # TOOD(nduca): When generic results objects are done, this special case | 295 # Report the desired results and details for each interval type. |
| 275 # can be replaced with a generic UI feature. | 296 intervals = [("frame", num_frames), ("second", num_seconds)] |
| 276 if thread_results.name in self.details_to_report: | 297 for (interval_name, num_intervals) in intervals: |
| 277 thread_results.AddDetailedResults(num_frames, results) | 298 for thread_results in thread_category_results.values(): |
| 299 if thread_results.name in self.results_to_report: | |
| 300 thread_results.AddResults(num_intervals, interval_name, results) | |
| 301 # TOOD(nduca): When generic results objects are done, this special case | |
| 302 # can be replaced with a generic UI feature. | |
| 303 if thread_results.name in self.details_to_report: | |
| 304 thread_results.AddDetailedResults( | |
| 305 num_intervals, interval_name, results) | |
| OLD | NEW |