1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
|
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import collections
from telemetry.util.statistics import DivideIfPossibleOrZero
from telemetry.web_perf.metrics import timeline_based_metric
from telemetry.value import scalar
class LoadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric):
def __init__(self):
super(LoadTimesTimelineMetric, self).__init__()
self.report_main_thread_only = True
def AddResults(self, model, renderer_thread, interaction_records, results):
assert model
assert len(interaction_records) == 1, (
'LoadTimesTimelineMetric cannot compute metrics for more than 1 time '
'range.')
interaction_record = interaction_records[0]
if self.report_main_thread_only:
thread_filter = 'CrRendererMain'
else:
thread_filter = None
events_by_name = collections.defaultdict(list)
renderer_process = renderer_thread.parent
for thread in renderer_process.threads.itervalues():
if thread_filter and not thread.name in thread_filter:
continue
thread_name = thread.name.replace('/','_')
for e in thread.IterAllSlicesInRange(interaction_record.start,
interaction_record.end):
events_by_name[e.name].append(e)
for event_name, event_group in events_by_name.iteritems():
times = [event.self_time for event in event_group]
total = sum(times)
biggest_jank = max(times)
# Results objects cannot contain the '.' character, so remove that here.
sanitized_event_name = event_name.replace('.', '_')
full_name = thread_name + '|' + sanitized_event_name
results.AddValue(scalar.ScalarValue(
results.current_page, full_name, 'ms', total))
results.AddValue(scalar.ScalarValue(
results.current_page, full_name + '_max', 'ms', biggest_jank))
results.AddValue(scalar.ScalarValue(
results.current_page, full_name + '_avg', 'ms', total / len(times)))
for counter_name, counter in renderer_process.counters.iteritems():
total = sum(counter.totals)
# Results objects cannot contain the '.' character, so remove that here.
sanitized_counter_name = counter_name.replace('.', '_')
results.AddValue(scalar.ScalarValue(
results.current_page, sanitized_counter_name, 'count', total))
results.AddValue(scalar.ScalarValue(
results.current_page, sanitized_counter_name + '_avg', 'count',
total / float(len(counter.totals))))
# We want to generate a consistant picture of our thread usage, despite
# having several process configurations (in-proc-gpu/single-proc).
# Since we can't isolate renderer threads in single-process mode, we
# always sum renderer-process threads' times. We also sum all io-threads
# for simplicity.
TimelineThreadCategories = {
"Chrome_InProcGpuThread": "GPU",
"CrGpuMain" : "GPU",
"AsyncTransferThread" : "GPU_transfer",
"CrBrowserMain" : "browser",
"Browser Compositor" : "browser",
"CrRendererMain" : "renderer_main",
"Compositor" : "renderer_compositor",
"IOThread" : "IO",
"CompositorRasterWorker": "raster",
"DummyThreadName1" : "other",
"DummyThreadName2" : "total_fast_path",
"DummyThreadName3" : "total_all"
}
_MatchBySubString = ["IOThread", "CompositorRasterWorker"]
AllThreads = TimelineThreadCategories.values()
NoThreads = []
FastPathThreads = ["GPU", "renderer_compositor", "browser", "IO"]
ReportMainThreadOnly = ["renderer_main"]
ReportSilkDetails = ["renderer_main"]
# TODO(epenner): Thread names above are likely fairly stable but trace names
# could change. We should formalize these traces to keep this robust.
OverheadTraceCategory = "trace_event_overhead"
OverheadTraceName = "overhead"
FrameTraceName = "::SwapBuffers"
FrameTraceThreadName = "renderer_compositor"
def Rate(numerator, denominator):
return DivideIfPossibleOrZero(numerator, denominator)
def ClockOverheadForEvent(event):
if (event.category == OverheadTraceCategory and
event.name == OverheadTraceName):
return event.duration
else:
return 0
def CpuOverheadForEvent(event):
if (event.category == OverheadTraceCategory and
event.thread_duration):
return event.thread_duration
else:
return 0
def ThreadCategoryName(thread_name):
thread_category = "other"
for substring, category in TimelineThreadCategories.iteritems():
if substring in _MatchBySubString and substring in thread_name:
thread_category = category
if thread_name in TimelineThreadCategories:
thread_category = TimelineThreadCategories[thread_name]
return thread_category
def ThreadCpuTimeResultName(thread_category):
# This isn't a good name, but I don't want to change it and lose continuity.
return "thread_" + thread_category + "_cpu_time_per_frame"
def ThreadTasksResultName(thread_category):
return "tasks_per_frame_" + thread_category
def ThreadMeanFrameTimeResultName(thread_category):
return "mean_frame_time_" + thread_category
def ThreadDetailResultName(thread_category, detail):
detail_sanitized = detail.replace('.','_')
return "thread_" + thread_category + "|" + detail_sanitized
class ResultsForThread(object):
def __init__(self, model, record_ranges, name):
self.model = model
self.toplevel_slices = []
self.all_slices = []
self.name = name
self.record_ranges = record_ranges
self.all_action_time = \
sum([record_range.bounds for record_range in self.record_ranges])
@property
def clock_time(self):
clock_duration = sum([x.duration for x in self.toplevel_slices])
clock_overhead = sum([ClockOverheadForEvent(x) for x in self.all_slices])
return clock_duration - clock_overhead
@property
def cpu_time(self):
cpu_duration = 0
cpu_overhead = sum([CpuOverheadForEvent(x) for x in self.all_slices])
for x in self.toplevel_slices:
# Only report thread-duration if we have it for all events.
#
# A thread_duration of 0 is valid, so this only returns 0 if it is None.
if x.thread_duration == None:
if not x.duration:
continue
else:
return 0
else:
cpu_duration += x.thread_duration
return cpu_duration - cpu_overhead
def SlicesInActions(self, slices):
slices_in_actions = []
for event in slices:
for record_range in self.record_ranges:
if record_range.ContainsInterval(event.start, event.end):
slices_in_actions.append(event)
break
return slices_in_actions
def AppendThreadSlices(self, thread):
self.all_slices.extend(self.SlicesInActions(thread.all_slices))
self.toplevel_slices.extend(self.SlicesInActions(thread.toplevel_slices))
# Currently we report cpu-time per frame, tasks per frame, and possibly
# the mean frame (if there is a trace specified to find it).
def AddResults(self, num_frames, results):
cpu_per_frame = Rate(self.cpu_time, num_frames)
tasks_per_frame = Rate(len(self.toplevel_slices), num_frames)
results.AddValue(scalar.ScalarValue(
results.current_page, ThreadCpuTimeResultName(self.name),
'ms', cpu_per_frame))
results.AddValue(scalar.ScalarValue(
results.current_page, ThreadTasksResultName(self.name),
'tasks', tasks_per_frame))
# Report mean frame time if this is the thread we are using for normalizing
# other results. We could report other frame rates (eg. renderer_main) but
# this might get confusing.
if self.name == FrameTraceThreadName:
num_frames = self.CountTracesWithName(FrameTraceName)
mean_frame_time = Rate(self.all_action_time, num_frames)
results.AddValue(scalar.ScalarValue(
results.current_page, ThreadMeanFrameTimeResultName(self.name),
'ms', mean_frame_time))
def AddDetailedResults(self, num_frames, results):
slices_by_category = collections.defaultdict(list)
for s in self.all_slices:
slices_by_category[s.category].append(s)
all_self_times = []
for category, slices_in_category in slices_by_category.iteritems():
self_time = sum([x.self_time for x in slices_in_category])
all_self_times.append(self_time)
self_time_result = (float(self_time) / num_frames) if num_frames else 0
results.AddValue(scalar.ScalarValue(
results.current_page, ThreadDetailResultName(self.name, category),
'ms', self_time_result))
all_measured_time = sum(all_self_times)
idle_time = max(0, self.all_action_time - all_measured_time)
idle_time_result = (float(idle_time) / num_frames) if num_frames else 0
results.AddValue(scalar.ScalarValue(
results.current_page, ThreadDetailResultName(self.name, "idle"),
'ms', idle_time_result))
def CountTracesWithName(self, substring):
count = 0
for event in self.all_slices:
if substring in event.name:
count += 1
return count
class ThreadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric):
def __init__(self):
super(ThreadTimesTimelineMetric, self).__init__()
# Minimal traces, for minimum noise in CPU-time measurements.
self.results_to_report = AllThreads
self.details_to_report = NoThreads
def AddResults(self, model, _, interaction_records, results):
# Set up each thread category for consistant results.
thread_category_results = {}
for name in TimelineThreadCategories.values():
thread_category_results[name] = ResultsForThread(
model, [r.GetBounds() for r in interaction_records], name)
# Group the slices by their thread category.
for thread in model.GetAllThreads():
thread_category = ThreadCategoryName(thread.name)
thread_category_results[thread_category].AppendThreadSlices(thread)
# Group all threads.
for thread in model.GetAllThreads():
thread_category_results['total_all'].AppendThreadSlices(thread)
# Also group fast-path threads.
for thread in model.GetAllThreads():
if ThreadCategoryName(thread.name) in FastPathThreads:
thread_category_results['total_fast_path'].AppendThreadSlices(thread)
# Calculate the number of frames.
frame_rate_thread = thread_category_results[FrameTraceThreadName]
num_frames = frame_rate_thread.CountTracesWithName(FrameTraceName)
# Report the desired results and details.
for thread_results in thread_category_results.values():
if thread_results.name in self.results_to_report:
thread_results.AddResults(num_frames, results)
# TOOD(nduca): When generic results objects are done, this special case
# can be replaced with a generic UI feature.
if thread_results.name in self.details_to_report:
thread_results.AddDetailedResults(num_frames, results)
|