Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / tools / perf / metrics / timeline.py
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.
4 import collections
5 from telemetry.util.statistics import DivideIfPossibleOrZero
6
7 from telemetry.web_perf.metrics import timeline_based_metric
8 from telemetry.value import scalar
9
10
11 class LoadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric):
12   def __init__(self):
13     super(LoadTimesTimelineMetric, self).__init__()
14     self.report_main_thread_only = True
15
16   def AddResults(self, model, renderer_thread, interaction_records, results):
17     assert model
18     assert len(interaction_records) == 1, (
19       'LoadTimesTimelineMetric cannot compute metrics for more than 1 time '
20       'range.')
21     interaction_record = interaction_records[0]
22     if self.report_main_thread_only:
23       thread_filter = 'CrRendererMain'
24     else:
25       thread_filter = None
26
27     events_by_name = collections.defaultdict(list)
28     renderer_process = renderer_thread.parent
29
30     for thread in renderer_process.threads.itervalues():
31
32       if thread_filter and not thread.name in thread_filter:
33         continue
34
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)
39
40       for event_name, event_group in events_by_name.iteritems():
41         times = [event.self_time for event in event_group]
42         total = sum(times)
43         biggest_jank = max(times)
44
45         # Results objects cannot contain the '.' character, so remove that here.
46         sanitized_event_name = event_name.replace('.', '_')
47
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)))
55
56     for counter_name, counter in renderer_process.counters.iteritems():
57       total = sum(counter.totals)
58
59       # Results objects cannot contain the '.' character, so remove that here.
60       sanitized_counter_name = counter_name.replace('.', '_')
61
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))))
67
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
72 # for simplicity.
73 TimelineThreadCategories =  {
74   "Chrome_InProcGpuThread": "GPU",
75   "CrGpuMain"             : "GPU",
76   "AsyncTransferThread"   : "GPU_transfer",
77   "CrBrowserMain"         : "browser",
78   "Browser Compositor"    : "browser",
79   "CrRendererMain"        : "renderer_main",
80   "Compositor"            : "renderer_compositor",
81   "IOThread"              : "IO",
82   "CompositorRasterWorker": "raster",
83   "DummyThreadName1"      : "other",
84   "DummyThreadName2"      : "total_fast_path",
85   "DummyThreadName3"      : "total_all"
86 }
87
88 _MatchBySubString = ["IOThread", "CompositorRasterWorker"]
89
90 AllThreads = TimelineThreadCategories.values()
91 NoThreads = []
92 FastPathThreads = ["GPU", "renderer_compositor", "browser", "IO"]
93
94 ReportMainThreadOnly = ["renderer_main"]
95 ReportSilkDetails = ["renderer_main"]
96
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"
103
104 def Rate(numerator, denominator):
105   return DivideIfPossibleOrZero(numerator, denominator)
106
107 def ClockOverheadForEvent(event):
108   if (event.category == OverheadTraceCategory and
109       event.name == OverheadTraceName):
110     return event.duration
111   else:
112     return 0
113
114 def CpuOverheadForEvent(event):
115   if (event.category == OverheadTraceCategory and
116       event.thread_duration):
117     return event.thread_duration
118   else:
119     return 0
120
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
129
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"
133
134 def ThreadTasksResultName(thread_category):
135   return "tasks_per_frame_" + thread_category
136
137 def ThreadMeanFrameTimeResultName(thread_category):
138   return "mean_frame_time_" + thread_category
139
140 def ThreadDetailResultName(thread_category, detail):
141   detail_sanitized = detail.replace('.','_')
142   return "thread_" + thread_category + "|" + detail_sanitized
143
144
145 class ResultsForThread(object):
146   def __init__(self, model, record_ranges, name):
147     self.model = model
148     self.toplevel_slices = []
149     self.all_slices = []
150     self.name = name
151     self.record_ranges = record_ranges
152     self.all_action_time = \
153         sum([record_range.bounds for record_range in self.record_ranges])
154
155   @property
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
160
161   @property
162   def cpu_time(self):
163     cpu_duration = 0
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.
167       #
168       # A thread_duration of 0 is valid, so this only returns 0 if it is None.
169       if x.thread_duration == None:
170         if not x.duration:
171           continue
172         else:
173           return 0
174       else:
175         cpu_duration += x.thread_duration
176     return cpu_duration - cpu_overhead
177
178   def SlicesInActions(self, slices):
179     slices_in_actions = []
180     for event in slices:
181       for record_range in self.record_ranges:
182         if record_range.ContainsInterval(event.start, event.end):
183           slices_in_actions.append(event)
184           break
185     return slices_in_actions
186
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))
190
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))
211
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)
216     all_self_times = []
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))
230
231   def CountTracesWithName(self, substring):
232     count = 0
233     for event in self.all_slices:
234       if substring in event.name:
235         count += 1
236     return count
237
238 class ThreadTimesTimelineMetric(timeline_based_metric.TimelineBasedMetric):
239   def __init__(self):
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
244
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)
251
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)
256
257     # Group all threads.
258     for thread in model.GetAllThreads():
259       thread_category_results['total_all'].AppendThreadSlices(thread)
260
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)
265
266     # Calculate the number of frames.
267     frame_rate_thread = thread_category_results[FrameTraceThreadName]
268     num_frames = frame_rate_thread.CountTracesWithName(FrameTraceName)
269
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)