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.web_perf.metrics import timeline_based_metric | 6 from telemetry.web_perf.metrics import timeline_based_metric |
| 7 from telemetry.value import scalar | 7 from telemetry.value import scalar |
| 8 | 8 |
| 9 | 9 |
| 10 class LoadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric): | 10 class LoadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric): |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 77 "Browser Compositor" : "browser", | 77 "Browser Compositor" : "browser", |
| 78 "CrRendererMain" : "renderer_main", | 78 "CrRendererMain" : "renderer_main", |
| 79 "Compositor" : "renderer_compositor", | 79 "Compositor" : "renderer_compositor", |
| 80 "IOThread" : "IO", | 80 "IOThread" : "IO", |
| 81 "CompositorRasterWorker": "raster", | 81 "CompositorRasterWorker": "raster", |
| 82 "DummyThreadName1" : "other", | 82 "DummyThreadName1" : "other", |
| 83 "DummyThreadName2" : "total_fast_path", | 83 "DummyThreadName2" : "total_fast_path", |
| 84 "DummyThreadName3" : "total_all" | 84 "DummyThreadName3" : "total_all" |
| 85 } | 85 } |
| 86 | 86 |
| 87 # Normalizing cpu-time by different rates might get confusing, but we could | |
| 88 # report different frame-rates by adding their associated trace here. | |
|
ernstm
2014/10/11 00:24:22
Why add this if we don't use it? You could instead
epenner
2014/10/13 22:20:32
Ahh, yeah my original goal was to remove those oth
| |
| 89 ThreadFrameTraceName = { | |
| 90 "renderer_compositor": "::SwapBuffers", | |
| 91 } | |
| 92 | |
| 87 _MatchBySubString = ["IOThread", "CompositorRasterWorker"] | 93 _MatchBySubString = ["IOThread", "CompositorRasterWorker"] |
| 88 | 94 |
| 89 AllThreads = TimelineThreadCategories.values() | 95 AllThreads = TimelineThreadCategories.values() |
| 90 NoThreads = [] | 96 NoThreads = [] |
| 91 FastPathThreads = ["GPU", "renderer_compositor", "browser", "IO"] | 97 FastPathThreads = ["GPU", "renderer_compositor", "browser", "IO"] |
| 92 | 98 |
| 93 ReportMainThreadOnly = ["renderer_main"] | 99 ReportMainThreadOnly = ["renderer_main"] |
| 94 ReportFastPathResults = AllThreads | 100 ReportFastPathResults = AllThreads |
| 95 ReportFastPathDetails = NoThreads | 101 ReportFastPathDetails = NoThreads |
| 96 ReportSilkResults = ["renderer_main", "total_all"] | 102 ReportSilkResults = ["renderer_main", "total_all"] |
| 97 ReportSilkDetails = ["renderer_main"] | 103 ReportSilkDetails = ["renderer_main"] |
| 98 | 104 |
| 99 # TODO(epenner): Thread names above are likely fairly stable but trace names | 105 # TODO(epenner): Thread names above are likely fairly stable but trace names |
| 100 # could change. We should formalize these traces to keep this robust. | 106 # could change. We should formalize these traces to keep this robust. |
| 101 OverheadTraceCategory = "trace_event_overhead" | 107 OverheadTraceCategory = "trace_event_overhead" |
| 102 OverheadTraceName = "overhead" | 108 OverheadTraceName = "overhead" |
| 103 FrameTraceName = "::SwapBuffers" | 109 FrameTraceName = "::SwapBuffers" |
| 104 FrameTraceThreadName = "renderer_compositor" | 110 FrameTraceThreadName = "renderer_compositor" |
| 105 | 111 |
| 112 def Rate(numerator, denominator): | |
|
ernstm
2014/10/11 00:24:22
We have telmetry.util.statistics.DivideIfPossibleO
epenner
2014/10/13 22:20:32
That doesn't cast to float. If you think I can cha
ernstm
2014/10/14 17:06:11
Please change DivideIfPossibleOrZero to cast to fl
| |
| 113 return (float(numerator) / denominator) if denominator else 0 | |
| 106 | 114 |
| 107 def ClockOverheadForEvent(event): | 115 def ClockOverheadForEvent(event): |
| 108 if (event.category == OverheadTraceCategory and | 116 if (event.category == OverheadTraceCategory and |
| 109 event.name == OverheadTraceName): | 117 event.name == OverheadTraceName): |
| 110 return event.duration | 118 return event.duration |
| 111 else: | 119 else: |
| 112 return 0 | 120 return 0 |
| 113 | 121 |
| 114 def CpuOverheadForEvent(event): | 122 def CpuOverheadForEvent(event): |
| 115 if (event.category == OverheadTraceCategory and | 123 if (event.category == OverheadTraceCategory and |
| 116 event.thread_duration): | 124 event.thread_duration): |
| 117 return event.thread_duration | 125 return event.thread_duration |
| 118 else: | 126 else: |
| 119 return 0 | 127 return 0 |
| 120 | 128 |
| 121 def ThreadCategoryName(thread_name): | 129 def ThreadCategoryName(thread_name): |
| 122 thread_category = "other" | 130 thread_category = "other" |
| 123 for substring, category in TimelineThreadCategories.iteritems(): | 131 for substring, category in TimelineThreadCategories.iteritems(): |
| 124 if substring in _MatchBySubString and substring in thread_name: | 132 if substring in _MatchBySubString and substring in thread_name: |
| 125 thread_category = category | 133 thread_category = category |
| 126 if thread_name in TimelineThreadCategories: | 134 if thread_name in TimelineThreadCategories: |
| 127 thread_category = TimelineThreadCategories[thread_name] | 135 thread_category = TimelineThreadCategories[thread_name] |
| 128 return thread_category | 136 return thread_category |
| 129 | 137 |
| 130 def ThreadTimeResultName(thread_category): | 138 def ThreadCpuTimeResultName(thread_category): |
| 131 return "thread_" + thread_category + "_clock_time_per_frame" | 139 # This name sucks but I don't want to change it and lose continuity |
|
ernstm
2014/10/11 00:24:22
Metrics can be migrated to new names on the dashbo
epenner
2014/10/13 22:20:32
That sounds cool. Got some documentation on that?
ernstm
2014/10/14 17:06:11
qyearsley can help with the data migration. It is
epenner
2014/10/18 01:02:30
Acknowledged.
| |
| 140 return "thread_" + thread_category + "_cpu_time_per_frame" | |
| 132 | 141 |
| 133 def ThreadCpuTimeResultName(thread_category): | 142 def ThreadTasksResultName(thread_category): |
| 134 return "thread_" + thread_category + "_cpu_time_per_frame" | 143 return "tasks_per_frame_" + thread_category |
| 144 | |
| 145 def ThreadFpsResultName(thread_category): | |
| 146 return "fps_" + thread_category | |
| 135 | 147 |
| 136 def ThreadDetailResultName(thread_category, detail): | 148 def ThreadDetailResultName(thread_category, detail): |
| 137 detail_sanitized = detail.replace('.','_') | 149 detail_sanitized = detail.replace('.','_') |
| 138 return "thread_" + thread_category + "|" + detail_sanitized | 150 return "thread_" + thread_category + "|" + detail_sanitized |
| 139 | 151 |
| 140 | 152 |
| 141 class ResultsForThread(object): | 153 class ResultsForThread(object): |
| 142 def __init__(self, model, record_ranges, name): | 154 def __init__(self, model, record_ranges, name): |
| 143 self.model = model | 155 self.model = model |
| 144 self.toplevel_slices = [] | 156 self.toplevel_slices = [] |
| 145 self.all_slices = [] | 157 self.all_slices = [] |
| 146 self.name = name | 158 self.name = name |
| 147 self.record_ranges = record_ranges | 159 self.record_ranges = record_ranges |
| 160 self.all_action_time = \ | |
| 161 sum([record_range.bounds for record_range in self.record_ranges]) | |
| 148 | 162 |
| 149 @property | 163 @property |
| 150 def clock_time(self): | 164 def clock_time(self): |
| 151 clock_duration = sum([x.duration for x in self.toplevel_slices]) | 165 clock_duration = sum([x.duration for x in self.toplevel_slices]) |
| 152 clock_overhead = sum([ClockOverheadForEvent(x) for x in self.all_slices]) | 166 clock_overhead = sum([ClockOverheadForEvent(x) for x in self.all_slices]) |
| 153 return clock_duration - clock_overhead | 167 return clock_duration - clock_overhead |
| 154 | 168 |
| 155 @property | 169 @property |
| 156 def cpu_time(self): | 170 def cpu_time(self): |
| 157 cpu_duration = 0 | 171 cpu_duration = 0 |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 175 for record_range in self.record_ranges: | 189 for record_range in self.record_ranges: |
| 176 if record_range.ContainsInterval(event.start, event.end): | 190 if record_range.ContainsInterval(event.start, event.end): |
| 177 slices_in_actions.append(event) | 191 slices_in_actions.append(event) |
| 178 break | 192 break |
| 179 return slices_in_actions | 193 return slices_in_actions |
| 180 | 194 |
| 181 def AppendThreadSlices(self, thread): | 195 def AppendThreadSlices(self, thread): |
| 182 self.all_slices.extend(self.SlicesInActions(thread.all_slices)) | 196 self.all_slices.extend(self.SlicesInActions(thread.all_slices)) |
| 183 self.toplevel_slices.extend(self.SlicesInActions(thread.toplevel_slices)) | 197 self.toplevel_slices.extend(self.SlicesInActions(thread.toplevel_slices)) |
| 184 | 198 |
| 199 # Currently we report cpu-time per frame, tasks per frame, and possibly | |
| 200 # the frame rate (if there is a trace specified to find it). | |
| 185 def AddResults(self, num_frames, results): | 201 def AddResults(self, num_frames, results): |
| 186 cpu_per_frame = (float(self.cpu_time) / num_frames) if num_frames else 0 | 202 cpu_per_frame = Rate(self.cpu_time, num_frames) |
| 203 tasks_per_frame = Rate(len(self.toplevel_slices), num_frames) | |
| 187 results.AddValue(scalar.ScalarValue( | 204 results.AddValue(scalar.ScalarValue( |
| 188 results.current_page, ThreadCpuTimeResultName(self.name), | 205 results.current_page, ThreadCpuTimeResultName(self.name), |
| 189 'ms', cpu_per_frame)) | 206 'ms', cpu_per_frame)) |
| 207 results.AddValue(scalar.ScalarValue( | |
| 208 results.current_page, ThreadTasksResultName(self.name), | |
| 209 'tasks', tasks_per_frame)) | |
| 210 if self.name in ThreadFrameTraceName: | |
| 211 num_frames = self.CountTracesWithName(ThreadFrameTraceName[self.name]) | |
| 212 frame_rate = Rate(num_frames, self.all_action_time) * 1000 | |
| 213 results.AddValue(scalar.ScalarValue( | |
| 214 results.current_page, ThreadFpsResultName(self.name), | |
| 215 'fps', frame_rate)) | |
| 190 | 216 |
| 191 def AddDetailedResults(self, num_frames, results): | 217 def AddDetailedResults(self, num_frames, results): |
| 192 slices_by_category = collections.defaultdict(list) | 218 slices_by_category = collections.defaultdict(list) |
| 193 for s in self.all_slices: | 219 for s in self.all_slices: |
| 194 slices_by_category[s.category].append(s) | 220 slices_by_category[s.category].append(s) |
| 195 all_self_times = [] | 221 all_self_times = [] |
| 196 for category, slices_in_category in slices_by_category.iteritems(): | 222 for category, slices_in_category in slices_by_category.iteritems(): |
| 197 self_time = sum([x.self_time for x in slices_in_category]) | 223 self_time = sum([x.self_time for x in slices_in_category]) |
| 198 all_self_times.append(self_time) | 224 all_self_times.append(self_time) |
| 199 self_time_result = (float(self_time) / num_frames) if num_frames else 0 | 225 self_time_result = (float(self_time) / num_frames) if num_frames else 0 |
| 200 results.AddValue(scalar.ScalarValue( | 226 results.AddValue(scalar.ScalarValue( |
| 201 results.current_page, ThreadDetailResultName(self.name, category), | 227 results.current_page, ThreadDetailResultName(self.name, category), |
| 202 'ms', self_time_result)) | 228 'ms', self_time_result)) |
| 203 all_measured_time = sum(all_self_times) | 229 all_measured_time = sum(all_self_times) |
| 204 all_action_time = \ | 230 idle_time = max(0, self.all_action_time - all_measured_time) |
| 205 sum([record_range.bounds for record_range in self.record_ranges]) | |
| 206 idle_time = max(0, all_action_time - all_measured_time) | |
| 207 idle_time_result = (float(idle_time) / num_frames) if num_frames else 0 | 231 idle_time_result = (float(idle_time) / num_frames) if num_frames else 0 |
| 208 results.AddValue(scalar.ScalarValue( | 232 results.AddValue(scalar.ScalarValue( |
| 209 results.current_page, ThreadDetailResultName(self.name, "idle"), | 233 results.current_page, ThreadDetailResultName(self.name, "idle"), |
| 210 'ms', idle_time_result)) | 234 'ms', idle_time_result)) |
| 211 | 235 |
| 236 def CountTracesWithName(self, substring): | |
| 237 count = 0 | |
| 238 for event in self.all_slices: | |
| 239 if substring in event.name: | |
| 240 count += 1 | |
| 241 return count | |
| 212 | 242 |
| 213 class ThreadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric): | 243 class ThreadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric): |
| 214 def __init__(self): | 244 def __init__(self): |
| 215 super(ThreadTimesTimelineMetric, self).__init__() | 245 super(ThreadTimesTimelineMetric, self).__init__() |
| 216 # Minimal traces, for minimum noise in CPU-time measurements. | 246 # Minimal traces, for minimum noise in CPU-time measurements. |
| 217 self.results_to_report = AllThreads | 247 self.results_to_report = AllThreads |
| 218 self.details_to_report = NoThreads | 248 self.details_to_report = NoThreads |
| 219 | 249 |
| 220 def CountSlices(self, slices, substring): | |
| 221 count = 0 | |
| 222 for event in slices: | |
| 223 if substring in event.name: | |
| 224 count += 1 | |
| 225 return count | |
| 226 | |
| 227 def AddResults(self, model, _, interaction_records, results): | 250 def AddResults(self, model, _, interaction_records, results): |
| 228 # Set up each thread category for consistant results. | 251 # Set up each thread category for consistant results. |
| 229 thread_category_results = {} | 252 thread_category_results = {} |
| 230 for name in TimelineThreadCategories.values(): | 253 for name in TimelineThreadCategories.values(): |
| 231 thread_category_results[name] = ResultsForThread( | 254 thread_category_results[name] = ResultsForThread( |
| 232 model, [r.GetBounds() for r in interaction_records], name) | 255 model, [r.GetBounds() for r in interaction_records], name) |
| 233 | 256 |
| 234 # Group the slices by their thread category. | 257 # Group the slices by their thread category. |
| 235 for thread in model.GetAllThreads(): | 258 for thread in model.GetAllThreads(): |
| 236 thread_category = ThreadCategoryName(thread.name) | 259 thread_category = ThreadCategoryName(thread.name) |
| 237 thread_category_results[thread_category].AppendThreadSlices(thread) | 260 thread_category_results[thread_category].AppendThreadSlices(thread) |
| 238 | 261 |
| 239 # Group all threads. | 262 # Group all threads. |
| 240 for thread in model.GetAllThreads(): | 263 for thread in model.GetAllThreads(): |
| 241 thread_category_results['total_all'].AppendThreadSlices(thread) | 264 thread_category_results['total_all'].AppendThreadSlices(thread) |
| 242 | 265 |
| 243 # Also group fast-path threads. | 266 # Also group fast-path threads. |
| 244 for thread in model.GetAllThreads(): | 267 for thread in model.GetAllThreads(): |
| 245 if ThreadCategoryName(thread.name) in FastPathThreads: | 268 if ThreadCategoryName(thread.name) in FastPathThreads: |
| 246 thread_category_results['total_fast_path'].AppendThreadSlices(thread) | 269 thread_category_results['total_fast_path'].AppendThreadSlices(thread) |
| 247 | 270 |
| 248 # Calculate the number of frames. | 271 # Calculate the number of frames / frame-rate. |
| 249 frame_slices = thread_category_results[FrameTraceThreadName].all_slices | 272 frame_rate_thread = thread_category_results[FrameTraceThreadName] |
| 250 num_frames = self.CountSlices(frame_slices, FrameTraceName) | 273 num_frames = frame_rate_thread.CountTracesWithName(FrameTraceName) |
| 251 | 274 |
| 252 # Report the desired results and details. | 275 # Report the desired results and details. |
| 253 for thread_results in thread_category_results.values(): | 276 for thread_results in thread_category_results.values(): |
| 254 if thread_results.name in self.results_to_report: | 277 if thread_results.name in self.results_to_report: |
| 255 thread_results.AddResults(num_frames, results) | 278 thread_results.AddResults(num_frames, results) |
| 256 # TOOD(nduca): When generic results objects are done, this special case | 279 # TOOD(nduca): When generic results objects are done, this special case |
| 257 # can be replaced with a generic UI feature. | 280 # can be replaced with a generic UI feature. |
| 258 if thread_results.name in self.details_to_report: | 281 if thread_results.name in self.details_to_report: |
| 259 thread_results.AddDetailedResults(num_frames, results) | 282 thread_results.AddDetailedResults(num_frames, results) |
| OLD | NEW |