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