aboutsummaryrefslogtreecommitdiff
path: root/catapult/systrace/systrace/output_generator.py
blob: 40bfe209200d8a5b71292bed5de175bf33dff64e (plain)
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
#!/usr/bin/env python

# Copyright 2016 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 base64
import gzip
import json
import os
import StringIO

from systrace import tracing_controller
from systrace import trace_result
from tracing.trace_data import trace_data


# TODO(alexandermont): Current version of trace viewer does not support
# the controller tracing agent output. Thus we use this variable to
# suppress this tracing agent's output. This should be removed once
# trace viewer is working again.
OUTPUT_CONTROLLER_TRACE_ = False
CONTROLLER_TRACE_DATA_KEY = 'controllerTraceDataKey'
_SYSTRACE_TO_TRACE_DATA_NAME_MAPPING = {
    'androidProcessDump': trace_data.ANDROID_PROCESS_DATA_PART,
    'atraceProcessDump': trace_data.ATRACE_PROCESS_DUMP_PART,
    'systemTraceEvents': trace_data.ATRACE_PART,
    'systraceController': trace_data.TELEMETRY_PART,
    'traceEvents': trace_data.CHROME_TRACE_PART,
    'waltTrace': trace_data.WALT_TRACE_PART,
    'cgroupDump': trace_data.CGROUP_TRACE_PART,
}
_SYSTRACE_HEADER = 'Systrace'


def NewGenerateHTMLOutput(trace_results, output_file_name):
  with trace_data.TraceDataBuilder() as builder:
    for trace in trace_results:
      trace_data_part = _SYSTRACE_TO_TRACE_DATA_NAME_MAPPING.get(
          trace.source_name)
      builder.AddTraceFor(
          trace_data_part, trace.raw_data, allow_unstructured=True)
    builder.Serialize(output_file_name, _SYSTRACE_HEADER)


def GenerateHTMLOutput(trace_results, output_file_name):
  """Write the results of systrace to an HTML file.

  Args:
      trace_results: A list of TraceResults.
      output_file_name: The name of the HTML file that the trace viewer
          results should be written to.
  """
  def _ReadAsset(src_dir, filename):
    return open(os.path.join(src_dir, filename)).read()

  # TODO(rnephew): The tracing output formatter is able to handle a single
  # systrace trace just as well as it handles multiple traces. The obvious thing
  # to do here would be to use it all for all systrace output: however, we want
  # to continue using the legacy way of formatting systrace output when a single
  # systrace and the tracing controller trace are present in order to match the
  # Java verison of systrace. Java systrace is expected to be deleted at a later
  # date. We should consolidate this logic when that happens.

  if len(trace_results) > 4:
    NewGenerateHTMLOutput(trace_results, output_file_name)
    return os.path.abspath(output_file_name)

  systrace_dir = os.path.abspath(os.path.dirname(__file__))

  try:
    from systrace import update_systrace_trace_viewer
  except ImportError:
    pass
  else:
    update_systrace_trace_viewer.update()

  trace_viewer_html = _ReadAsset(systrace_dir, 'systrace_trace_viewer.html')

  # Open the file in binary mode to prevent python from changing the
  # line endings, then write the prefix.
  systrace_dir = os.path.abspath(os.path.dirname(__file__))
  html_prefix = _ReadAsset(systrace_dir, 'prefix.html.template')
  html_suffix = _ReadAsset(systrace_dir, 'suffix.html')
  trace_viewer_html = _ReadAsset(systrace_dir,
                                  'systrace_trace_viewer.html')
  catapult_root = os.path.abspath(os.path.dirname(os.path.dirname(
                                  os.path.dirname(__file__))))
  polymer_dir = os.path.join(catapult_root, 'third_party', 'polymer',
                             'components', 'webcomponentsjs')
  webcomponent_v0_polyfill = _ReadAsset(polymer_dir, 'webcomponents.min.js')

  # Add the polyfill
  html_output = html_prefix.replace('{{WEBCOMPONENTS_V0_POLYFILL_JS}}',
                                    webcomponent_v0_polyfill)

  # Open the file in binary mode to prevent python from changing the
  # line endings, then write the prefix.
  html_file = open(output_file_name, 'wb')
  html_file.write(html_output.replace('{{SYSTRACE_TRACE_VIEWER_HTML}}',
                                      trace_viewer_html))



  # Write the trace data itself. There is a separate section of the form
  # <script class="trace-data" type="application/text"> ... </script>
  # for each tracing agent (including the controller tracing agent).
  html_file.write('<!-- BEGIN TRACE -->\n')
  for result in trace_results:
    html_file.write('  <script class="trace-data" type="application/text">\n')
    html_file.write(_ConvertToHtmlString(result.raw_data))
    html_file.write('  </script>\n')
  html_file.write('<!-- END TRACE -->\n')

  # Write the suffix and finish.
  html_file.write(html_suffix)
  html_file.close()

  final_path = os.path.abspath(output_file_name)
  return final_path

def _ConvertToHtmlString(result):
  """Convert a trace result to the format to be output into HTML.

  If the trace result is a dictionary or list, JSON-encode it.
  If the trace result is a string, leave it unchanged.
  """
  if isinstance(result, dict) or isinstance(result, list):
    return json.dumps(result)
  elif isinstance(result, str):
    return result
  else:
    raise ValueError('Invalid trace result format for HTML output')

def GenerateJSONOutput(trace_results, output_file_name):
  """Write the results of systrace to a JSON file.

  Args:
      trace_results: A list of TraceResults.
      output_file_name: The name of the JSON file that the trace viewer
          results should be written to.
  """
  results = _ConvertTraceListToDictionary(trace_results)
  results[CONTROLLER_TRACE_DATA_KEY] = (
      tracing_controller.TRACE_DATA_CONTROLLER_NAME)
  with open(output_file_name, 'w') as json_file:
    json.dump(results, json_file)
  final_path = os.path.abspath(output_file_name)
  return final_path

def MergeTraceResultsIfNeeded(trace_results):
  """Merge a list of trace data, if possible. This function can take any list
     of trace data, but it will only merge the JSON data (since that's all
     we can merge).

     Args:
        trace_results: A list of TraceResults containing trace data.
  """
  if len(trace_results) <= 1:
    return trace_results
  merge_candidates = []
  for result in trace_results:
    # Try to detect a JSON file cheaply since that's all we can merge.
    if result.raw_data[0] != '{':
      continue
    try:
      json_data = json.loads(result.raw_data)
    except ValueError:
      continue
    merge_candidates.append(trace_result.TraceResult(result.source_name,
                                                     json_data))

  if len(merge_candidates) <= 1:
    return trace_results

  other_results = [r for r in trace_results
                   if not r.source_name in
                   [c.source_name for c in merge_candidates]]

  merged_data = merge_candidates[0].raw_data

  for candidate in merge_candidates[1:]:
    json_data = candidate.raw_data
    for key, value in json_data.items():
      if not str(key) in merged_data or not merged_data[str(key)]:
        merged_data[str(key)] = value

  return ([trace_result.TraceResult('merged-data', json.dumps(merged_data))]
              + other_results)

def _EncodeTraceData(trace_string):
  compressed_trace = StringIO.StringIO()
  with gzip.GzipFile(fileobj=compressed_trace, mode='w') as f:
    f.write(trace_string)
  b64_content = base64.b64encode(compressed_trace.getvalue())
  return b64_content

def _ConvertTraceListToDictionary(trace_list):
  trace_dict = {}
  for trace in trace_list:
    trace_dict[trace.source_name] = trace.raw_data
  return trace_dict