• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2
3# Copyright 2016 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7import base64
8import gzip
9import json
10import os
11import StringIO
12
13from systrace import tracing_controller
14from systrace import trace_result
15from tracing.trace_data import trace_data
16
17
18# TODO(alexandermont): Current version of trace viewer does not support
19# the controller tracing agent output. Thus we use this variable to
20# suppress this tracing agent's output. This should be removed once
21# trace viewer is working again.
22OUTPUT_CONTROLLER_TRACE_ = False
23CONTROLLER_TRACE_DATA_KEY = 'controllerTraceDataKey'
24_SYSTRACE_TO_TRACE_DATA_NAME_MAPPING = {
25    'androidProcessDump': trace_data.ANDROID_PROCESS_DATA_PART,
26    'atraceProcessDump': trace_data.ATRACE_PROCESS_DUMP_PART,
27    'systemTraceEvents': trace_data.ATRACE_PART,
28    'systraceController': trace_data.TELEMETRY_PART,
29    'traceEvents': trace_data.CHROME_TRACE_PART,
30    'waltTrace': trace_data.WALT_TRACE_PART,
31    'cgroupDump': trace_data.CGROUP_TRACE_PART,
32}
33_SYSTRACE_HEADER = 'Systrace'
34
35
36def NewGenerateHTMLOutput(trace_results, output_file_name):
37  with trace_data.TraceDataBuilder() as builder:
38    for trace in trace_results:
39      trace_data_part = _SYSTRACE_TO_TRACE_DATA_NAME_MAPPING.get(
40          trace.source_name)
41      builder.AddTraceFor(
42          trace_data_part, trace.raw_data, allow_unstructured=True)
43    builder.Serialize(output_file_name, _SYSTRACE_HEADER)
44
45
46def GenerateHTMLOutput(trace_results, output_file_name):
47  """Write the results of systrace to an HTML file.
48
49  Args:
50      trace_results: A list of TraceResults.
51      output_file_name: The name of the HTML file that the trace viewer
52          results should be written to.
53  """
54  def _ReadAsset(src_dir, filename):
55    return open(os.path.join(src_dir, filename)).read()
56
57  # TODO(rnephew): The tracing output formatter is able to handle a single
58  # systrace trace just as well as it handles multiple traces. The obvious thing
59  # to do here would be to use it all for all systrace output: however, we want
60  # to continue using the legacy way of formatting systrace output when a single
61  # systrace and the tracing controller trace are present in order to match the
62  # Java verison of systrace. Java systrace is expected to be deleted at a later
63  # date. We should consolidate this logic when that happens.
64
65  if len(trace_results) > 4:
66    NewGenerateHTMLOutput(trace_results, output_file_name)
67    return os.path.abspath(output_file_name)
68
69  systrace_dir = os.path.abspath(os.path.dirname(__file__))
70
71  try:
72    from systrace import update_systrace_trace_viewer
73  except ImportError:
74    pass
75  else:
76    update_systrace_trace_viewer.update()
77
78  trace_viewer_html = _ReadAsset(systrace_dir, 'systrace_trace_viewer.html')
79
80  # Open the file in binary mode to prevent python from changing the
81  # line endings, then write the prefix.
82  systrace_dir = os.path.abspath(os.path.dirname(__file__))
83  html_prefix = _ReadAsset(systrace_dir, 'prefix.html.template')
84  html_suffix = _ReadAsset(systrace_dir, 'suffix.html')
85  trace_viewer_html = _ReadAsset(systrace_dir,
86                                  'systrace_trace_viewer.html')
87  catapult_root = os.path.abspath(os.path.dirname(os.path.dirname(
88                                  os.path.dirname(__file__))))
89  polymer_dir = os.path.join(catapult_root, 'third_party', 'polymer',
90                             'components', 'webcomponentsjs')
91  webcomponent_v0_polyfill = _ReadAsset(polymer_dir, 'webcomponents.min.js')
92
93  # Add the polyfill
94  html_output = html_prefix.replace('{{WEBCOMPONENTS_V0_POLYFILL_JS}}',
95                                    webcomponent_v0_polyfill)
96
97  # Open the file in binary mode to prevent python from changing the
98  # line endings, then write the prefix.
99  html_file = open(output_file_name, 'wb')
100  html_file.write(html_output.replace('{{SYSTRACE_TRACE_VIEWER_HTML}}',
101                                      trace_viewer_html))
102
103
104
105  # Write the trace data itself. There is a separate section of the form
106  # <script class="trace-data" type="application/text"> ... </script>
107  # for each tracing agent (including the controller tracing agent).
108  html_file.write('<!-- BEGIN TRACE -->\n')
109  for result in trace_results:
110    html_file.write('  <script class="trace-data" type="application/text">\n')
111    html_file.write(_ConvertToHtmlString(result.raw_data))
112    html_file.write('  </script>\n')
113  html_file.write('<!-- END TRACE -->\n')
114
115  # Write the suffix and finish.
116  html_file.write(html_suffix)
117  html_file.close()
118
119  final_path = os.path.abspath(output_file_name)
120  return final_path
121
122def _ConvertToHtmlString(result):
123  """Convert a trace result to the format to be output into HTML.
124
125  If the trace result is a dictionary or list, JSON-encode it.
126  If the trace result is a string, leave it unchanged.
127  """
128  if isinstance(result, dict) or isinstance(result, list):
129    return json.dumps(result)
130  elif isinstance(result, str):
131    return result
132  else:
133    raise ValueError('Invalid trace result format for HTML output')
134
135def GenerateJSONOutput(trace_results, output_file_name):
136  """Write the results of systrace to a JSON file.
137
138  Args:
139      trace_results: A list of TraceResults.
140      output_file_name: The name of the JSON file that the trace viewer
141          results should be written to.
142  """
143  results = _ConvertTraceListToDictionary(trace_results)
144  results[CONTROLLER_TRACE_DATA_KEY] = (
145      tracing_controller.TRACE_DATA_CONTROLLER_NAME)
146  with open(output_file_name, 'w') as json_file:
147    json.dump(results, json_file)
148  final_path = os.path.abspath(output_file_name)
149  return final_path
150
151def MergeTraceResultsIfNeeded(trace_results):
152  """Merge a list of trace data, if possible. This function can take any list
153     of trace data, but it will only merge the JSON data (since that's all
154     we can merge).
155
156     Args:
157        trace_results: A list of TraceResults containing trace data.
158  """
159  if len(trace_results) <= 1:
160    return trace_results
161  merge_candidates = []
162  for result in trace_results:
163    # Try to detect a JSON file cheaply since that's all we can merge.
164    if result.raw_data[0] != '{':
165      continue
166    try:
167      json_data = json.loads(result.raw_data)
168    except ValueError:
169      continue
170    merge_candidates.append(trace_result.TraceResult(result.source_name,
171                                                     json_data))
172
173  if len(merge_candidates) <= 1:
174    return trace_results
175
176  other_results = [r for r in trace_results
177                   if not r.source_name in
178                   [c.source_name for c in merge_candidates]]
179
180  merged_data = merge_candidates[0].raw_data
181
182  for candidate in merge_candidates[1:]:
183    json_data = candidate.raw_data
184    for key, value in json_data.items():
185      if not str(key) in merged_data or not merged_data[str(key)]:
186        merged_data[str(key)] = value
187
188  return ([trace_result.TraceResult('merged-data', json.dumps(merged_data))]
189              + other_results)
190
191def _EncodeTraceData(trace_string):
192  compressed_trace = StringIO.StringIO()
193  with gzip.GzipFile(fileobj=compressed_trace, mode='w') as f:
194    f.write(trace_string)
195  b64_content = base64.b64encode(compressed_trace.getvalue())
196  return b64_content
197
198def _ConvertTraceListToDictionary(trace_list):
199  trace_dict = {}
200  for trace in trace_list:
201    trace_dict[trace.source_name] = trace.raw_data
202  return trace_dict
203