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
3 # found in the LICENSE file.
6 from telemetry.web_perf.metrics import timeline_based_metric
7 from telemetry.value import scalar
10 class LoadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric):
12 super(LoadTimesTimelineMetric, self).__init__()
13 self.report_main_thread_only = True
15 def AddResults(self, model, renderer_thread, interaction_records, results):
17 assert len(interaction_records) == 1, (
18 'LoadTimesTimelineMetric cannot compute metrics for more than 1 time '
20 interaction_record = interaction_records[0]
21 if self.report_main_thread_only:
22 thread_filter = 'CrRendererMain'
26 events_by_name = collections.defaultdict(list)
27 renderer_process = renderer_thread.parent
29 for thread in renderer_process.threads.itervalues():
31 if thread_filter and not thread.name in thread_filter:
34 thread_name = thread.name.replace('/','_')
35 for e in thread.IterAllSlicesInRange(interaction_record.start,
36 interaction_record.end):
37 events_by_name[e.name].append(e)
39 for event_name, event_group in events_by_name.iteritems():
40 times = [event.self_time for event in event_group]
42 biggest_jank = max(times)
44 # Results objects cannot contain the '.' character, so remove that here.
45 sanitized_event_name = event_name.replace('.', '_')
47 full_name = thread_name + '|' + sanitized_event_name
48 results.AddValue(scalar.ScalarValue(
49 results.current_page, full_name, 'ms', total))
50 results.AddValue(scalar.ScalarValue(
51 results.current_page, full_name + '_max', 'ms', biggest_jank))
52 results.AddValue(scalar.ScalarValue(
53 results.current_page, full_name + '_avg', 'ms', total / len(times)))
55 for counter_name, counter in renderer_process.counters.iteritems():
56 total = sum(counter.totals)
58 # Results objects cannot contain the '.' character, so remove that here.
59 sanitized_counter_name = counter_name.replace('.', '_')
61 results.AddValue(scalar.ScalarValue(
62 results.current_page, sanitized_counter_name, 'count', total))
63 results.AddValue(scalar.ScalarValue(
64 results.current_page, sanitized_counter_name + '_avg', 'count',
65 total / float(len(counter.totals))))
67 # We want to generate a consistant picture of our thread usage, despite
68 # having several process configurations (in-proc-gpu/single-proc).
69 # Since we can't isolate renderer threads in single-process mode, we
70 # always sum renderer-process threads' times. We also sum all io-threads
72 TimelineThreadCategories = {
73 "Chrome_InProcGpuThread": "GPU",
75 "AsyncTransferThread" : "GPU_transfer",
76 "CrBrowserMain" : "browser",
77 "Browser Compositor" : "browser",
78 "CrRendererMain" : "renderer_main",
79 "Compositor" : "renderer_compositor",
81 "CompositorRasterWorker": "raster",
82 "DummyThreadName1" : "other",
83 "DummyThreadName2" : "total_fast_path",
84 "DummyThreadName3" : "total_all"
87 _MatchBySubString = ["IOThread", "CompositorRasterWorker"]
89 AllThreads = TimelineThreadCategories.values()
91 FastPathThreads = ["GPU", "renderer_compositor", "browser", "IO"]
93 ReportMainThreadOnly = ["renderer_main"]
94 ReportFastPathResults = AllThreads
95 ReportFastPathDetails = NoThreads
96 ReportSilkResults = ["renderer_main", "total_all"]
97 ReportSilkDetails = ["renderer_main"]
99 # TODO(epenner): Thread names above are likely fairly stable but trace names
100 # could change. We should formalize these traces to keep this robust.
101 OverheadTraceCategory = "trace_event_overhead"
102 OverheadTraceName = "overhead"
103 FrameTraceName = "::SwapBuffers"
104 FrameTraceThreadName = "renderer_compositor"
107 def ClockOverheadForEvent(event):
108 if (event.category == OverheadTraceCategory and
109 event.name == OverheadTraceName):
110 return event.duration
114 def CpuOverheadForEvent(event):
115 if (event.category == OverheadTraceCategory and
116 event.thread_duration):
117 return event.thread_duration
121 def ThreadCategoryName(thread_name):
122 thread_category = "other"
123 for substring, category in TimelineThreadCategories.iteritems():
124 if substring in _MatchBySubString and substring in thread_name:
125 thread_category = category
126 if thread_name in TimelineThreadCategories:
127 thread_category = TimelineThreadCategories[thread_name]
128 return thread_category
130 def ThreadTimeResultName(thread_category):
131 return "thread_" + thread_category + "_clock_time_per_frame"
133 def ThreadCpuTimeResultName(thread_category):
134 return "thread_" + thread_category + "_cpu_time_per_frame"
136 def ThreadDetailResultName(thread_category, detail):
137 detail_sanitized = detail.replace('.','_')
138 return "thread_" + thread_category + "|" + detail_sanitized
141 class ResultsForThread(object):
142 def __init__(self, model, record_ranges, name):
144 self.toplevel_slices = []
147 self.record_ranges = record_ranges
150 def clock_time(self):
151 clock_duration = sum([x.duration for x in self.toplevel_slices])
152 clock_overhead = sum([ClockOverheadForEvent(x) for x in self.all_slices])
153 return clock_duration - clock_overhead
158 cpu_overhead = sum([CpuOverheadForEvent(x) for x in self.all_slices])
159 for x in self.toplevel_slices:
160 # Only report thread-duration if we have it for all events.
162 # A thread_duration of 0 is valid, so this only returns 0 if it is None.
163 if x.thread_duration == None:
169 cpu_duration += x.thread_duration
170 return cpu_duration - cpu_overhead
172 def SlicesInActions(self, slices):
173 slices_in_actions = []
175 for record_range in self.record_ranges:
176 if record_range.ContainsInterval(event.start, event.end):
177 slices_in_actions.append(event)
179 return slices_in_actions
181 def AppendThreadSlices(self, thread):
182 self.all_slices.extend(self.SlicesInActions(thread.all_slices))
183 self.toplevel_slices.extend(self.SlicesInActions(thread.toplevel_slices))
185 def AddResults(self, num_frames, results):
186 cpu_per_frame = (float(self.cpu_time) / num_frames) if num_frames else 0
187 results.AddValue(scalar.ScalarValue(
188 results.current_page, ThreadCpuTimeResultName(self.name),
189 'ms', cpu_per_frame))
191 def AddDetailedResults(self, num_frames, results):
192 slices_by_category = collections.defaultdict(list)
193 for s in self.all_slices:
194 slices_by_category[s.category].append(s)
196 for category, slices_in_category in slices_by_category.iteritems():
197 self_time = sum([x.self_time for x in slices_in_category])
198 all_self_times.append(self_time)
199 self_time_result = (float(self_time) / num_frames) if num_frames else 0
200 results.AddValue(scalar.ScalarValue(
201 results.current_page, ThreadDetailResultName(self.name, category),
202 'ms', self_time_result))
203 all_measured_time = sum(all_self_times)
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
208 results.AddValue(scalar.ScalarValue(
209 results.current_page, ThreadDetailResultName(self.name, "idle"),
210 'ms', idle_time_result))
213 class ThreadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric):
215 super(ThreadTimesTimelineMetric, self).__init__()
216 # Minimal traces, for minimum noise in CPU-time measurements.
217 self.results_to_report = AllThreads
218 self.details_to_report = NoThreads
220 def CountSlices(self, slices, substring):
223 if substring in event.name:
227 def AddResults(self, model, _, interaction_records, results):
228 # Set up each thread category for consistant results.
229 thread_category_results = {}
230 for name in TimelineThreadCategories.values():
231 thread_category_results[name] = ResultsForThread(
232 model, [r.GetBounds() for r in interaction_records], name)
234 # Group the slices by their thread category.
235 for thread in model.GetAllThreads():
236 thread_category = ThreadCategoryName(thread.name)
237 thread_category_results[thread_category].AppendThreadSlices(thread)
240 for thread in model.GetAllThreads():
241 thread_category_results['total_all'].AppendThreadSlices(thread)
243 # Also group fast-path threads.
244 for thread in model.GetAllThreads():
245 if ThreadCategoryName(thread.name) in FastPathThreads:
246 thread_category_results['total_fast_path'].AppendThreadSlices(thread)
248 # Calculate the number of frames.
249 frame_slices = thread_category_results[FrameTraceThreadName].all_slices
250 num_frames = self.CountSlices(frame_slices, FrameTraceName)
252 # Report the desired results and details.
253 for thread_results in thread_category_results.values():
254 if thread_results.name in self.results_to_report:
255 thread_results.AddResults(num_frames, results)
256 # TOOD(nduca): When generic results objects are done, this special case
257 # can be replaced with a generic UI feature.
258 if thread_results.name in self.details_to_report:
259 thread_results.AddDetailedResults(num_frames, results)