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.
5 from telemetry
.util
.statistics
import DivideIfPossibleOrZero
7 from telemetry
.web_perf
.metrics
import timeline_based_metric
8 from telemetry
.value
import scalar
11 class LoadTimesTimelineMetric(timeline_based_metric
.TimelineBasedMetric
):
13 super(LoadTimesTimelineMetric
, self
).__init
__()
14 self
.report_main_thread_only
= True
16 def AddResults(self
, model
, renderer_thread
, interaction_records
, results
):
18 assert len(interaction_records
) == 1, (
19 'LoadTimesTimelineMetric cannot compute metrics for more than 1 time '
21 interaction_record
= interaction_records
[0]
22 if self
.report_main_thread_only
:
23 thread_filter
= 'CrRendererMain'
27 events_by_name
= collections
.defaultdict(list)
28 renderer_process
= renderer_thread
.parent
30 for thread
in renderer_process
.threads
.itervalues():
32 if thread_filter
and not thread
.name
in thread_filter
:
35 thread_name
= thread
.name
.replace('/','_')
36 for e
in thread
.IterAllSlicesInRange(interaction_record
.start
,
37 interaction_record
.end
):
38 events_by_name
[e
.name
].append(e
)
40 for event_name
, event_group
in events_by_name
.iteritems():
41 times
= [event
.self_time
for event
in event_group
]
43 biggest_jank
= max(times
)
45 # Results objects cannot contain the '.' character, so remove that here.
46 sanitized_event_name
= event_name
.replace('.', '_')
48 full_name
= thread_name
+ '|' + sanitized_event_name
49 results
.AddValue(scalar
.ScalarValue(
50 results
.current_page
, full_name
, 'ms', total
))
51 results
.AddValue(scalar
.ScalarValue(
52 results
.current_page
, full_name
+ '_max', 'ms', biggest_jank
))
53 results
.AddValue(scalar
.ScalarValue(
54 results
.current_page
, full_name
+ '_avg', 'ms', total
/ len(times
)))
56 for counter_name
, counter
in renderer_process
.counters
.iteritems():
57 total
= sum(counter
.totals
)
59 # Results objects cannot contain the '.' character, so remove that here.
60 sanitized_counter_name
= counter_name
.replace('.', '_')
62 results
.AddValue(scalar
.ScalarValue(
63 results
.current_page
, sanitized_counter_name
, 'count', total
))
64 results
.AddValue(scalar
.ScalarValue(
65 results
.current_page
, sanitized_counter_name
+ '_avg', 'count',
66 total
/ float(len(counter
.totals
))))
68 # We want to generate a consistant picture of our thread usage, despite
69 # having several process configurations (in-proc-gpu/single-proc).
70 # Since we can't isolate renderer threads in single-process mode, we
71 # always sum renderer-process threads' times. We also sum all io-threads
73 TimelineThreadCategories
= {
74 "Chrome_InProcGpuThread": "GPU",
76 "AsyncTransferThread" : "GPU_transfer",
77 "CrBrowserMain" : "browser",
78 "Browser Compositor" : "browser",
79 "CrRendererMain" : "renderer_main",
80 "Compositor" : "renderer_compositor",
82 "CompositorRasterWorker": "raster",
83 "DummyThreadName1" : "other",
84 "DummyThreadName2" : "total_fast_path",
85 "DummyThreadName3" : "total_all"
88 _MatchBySubString
= ["IOThread", "CompositorRasterWorker"]
90 AllThreads
= TimelineThreadCategories
.values()
92 FastPathThreads
= ["GPU", "renderer_compositor", "browser", "IO"]
94 ReportMainThreadOnly
= ["renderer_main"]
95 ReportSilkDetails
= ["renderer_main"]
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.
99 OverheadTraceCategory
= "trace_event_overhead"
100 OverheadTraceName
= "overhead"
101 FrameTraceName
= "::SwapBuffers"
102 FrameTraceThreadName
= "renderer_compositor"
104 def Rate(numerator
, denominator
):
105 return DivideIfPossibleOrZero(numerator
, denominator
)
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 ThreadCpuTimeResultName(thread_category
):
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"
134 def ThreadTasksResultName(thread_category
):
135 return "tasks_per_frame_" + thread_category
137 def ThreadMeanFrameTimeResultName(thread_category
):
138 return "mean_frame_time_" + thread_category
140 def ThreadDetailResultName(thread_category
, detail
):
141 detail_sanitized
= detail
.replace('.','_')
142 return "thread_" + thread_category
+ "|" + detail_sanitized
145 class ResultsForThread(object):
146 def __init__(self
, model
, record_ranges
, name
):
148 self
.toplevel_slices
= []
151 self
.record_ranges
= record_ranges
152 self
.all_action_time
= \
153 sum([record_range
.bounds
for record_range
in self
.record_ranges
])
156 def clock_time(self
):
157 clock_duration
= sum([x
.duration
for x
in self
.toplevel_slices
])
158 clock_overhead
= sum([ClockOverheadForEvent(x
) for x
in self
.all_slices
])
159 return clock_duration
- clock_overhead
164 cpu_overhead
= sum([CpuOverheadForEvent(x
) for x
in self
.all_slices
])
165 for x
in self
.toplevel_slices
:
166 # Only report thread-duration if we have it for all events.
168 # A thread_duration of 0 is valid, so this only returns 0 if it is None.
169 if x
.thread_duration
== None:
175 cpu_duration
+= x
.thread_duration
176 return cpu_duration
- cpu_overhead
178 def SlicesInActions(self
, slices
):
179 slices_in_actions
= []
181 for record_range
in self
.record_ranges
:
182 if record_range
.ContainsInterval(event
.start
, event
.end
):
183 slices_in_actions
.append(event
)
185 return slices_in_actions
187 def AppendThreadSlices(self
, thread
):
188 self
.all_slices
.extend(self
.SlicesInActions(thread
.all_slices
))
189 self
.toplevel_slices
.extend(self
.SlicesInActions(thread
.toplevel_slices
))
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).
193 def AddResults(self
, num_frames
, results
):
194 cpu_per_frame
= Rate(self
.cpu_time
, num_frames
)
195 tasks_per_frame
= Rate(len(self
.toplevel_slices
), num_frames
)
196 results
.AddValue(scalar
.ScalarValue(
197 results
.current_page
, ThreadCpuTimeResultName(self
.name
),
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
))
212 def AddDetailedResults(self
, num_frames
, results
):
213 slices_by_category
= collections
.defaultdict(list)
214 for s
in self
.all_slices
:
215 slices_by_category
[s
.category
].append(s
)
217 for category
, slices_in_category
in slices_by_category
.iteritems():
218 self_time
= sum([x
.self_time
for x
in slices_in_category
])
219 all_self_times
.append(self_time
)
220 self_time_result
= (float(self_time
) / num_frames
) if num_frames
else 0
221 results
.AddValue(scalar
.ScalarValue(
222 results
.current_page
, ThreadDetailResultName(self
.name
, category
),
223 'ms', self_time_result
))
224 all_measured_time
= sum(all_self_times
)
225 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
227 results
.AddValue(scalar
.ScalarValue(
228 results
.current_page
, ThreadDetailResultName(self
.name
, "idle"),
229 'ms', idle_time_result
))
231 def CountTracesWithName(self
, substring
):
233 for event
in self
.all_slices
:
234 if substring
in event
.name
:
238 class ThreadTimesTimelineMetric(timeline_based_metric
.TimelineBasedMetric
):
240 super(ThreadTimesTimelineMetric
, self
).__init
__()
241 # Minimal traces, for minimum noise in CPU-time measurements.
242 self
.results_to_report
= AllThreads
243 self
.details_to_report
= NoThreads
245 def AddResults(self
, model
, _
, interaction_records
, results
):
246 # Set up each thread category for consistant results.
247 thread_category_results
= {}
248 for name
in TimelineThreadCategories
.values():
249 thread_category_results
[name
] = ResultsForThread(
250 model
, [r
.GetBounds() for r
in interaction_records
], name
)
252 # Group the slices by their thread category.
253 for thread
in model
.GetAllThreads():
254 thread_category
= ThreadCategoryName(thread
.name
)
255 thread_category_results
[thread_category
].AppendThreadSlices(thread
)
258 for thread
in model
.GetAllThreads():
259 thread_category_results
['total_all'].AppendThreadSlices(thread
)
261 # Also group fast-path threads.
262 for thread
in model
.GetAllThreads():
263 if ThreadCategoryName(thread
.name
) in FastPathThreads
:
264 thread_category_results
['total_fast_path'].AppendThreadSlices(thread
)
266 # Calculate the number of frames.
267 frame_rate_thread
= thread_category_results
[FrameTraceThreadName
]
268 num_frames
= frame_rate_thread
.CountTracesWithName(FrameTraceName
)
270 # Report the desired results and details.
271 for thread_results
in thread_category_results
.values():
272 if thread_results
.name
in self
.results_to_report
:
273 thread_results
.AddResults(num_frames
, results
)
274 # TOOD(nduca): When generic results objects are done, this special case
275 # can be replaced with a generic UI feature.
276 if thread_results
.name
in self
.details_to_report
:
277 thread_results
.AddDetailedResults(num_frames
, results
)