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