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 'systemTraceEvents': trace_data.ATRACE_PART, 27 'powerTraceAsString': trace_data.BATTOR_TRACE_PART, 28 'systraceController': trace_data.TELEMETRY_PART, 29 'traceEvents': trace_data.CHROME_TRACE_PART, 30 'waltTrace': trace_data.WALT_TRACE_PART, 31} 32_SYSTRACE_HEADER = 'Systrace' 33 34 35def NewGenerateHTMLOutput(trace_results, output_file_name): 36 trace_data_builder = trace_data.TraceDataBuilder() 37 for trace in trace_results: 38 trace_data_part = _SYSTRACE_TO_TRACE_DATA_NAME_MAPPING.get( 39 trace.source_name) 40 trace_data_builder.AddTraceFor(trace_data_part, trace.raw_data) 41 trace_data_builder.AsData().Serialize(output_file_name, _SYSTRACE_HEADER) 42 43 44def GenerateHTMLOutput(trace_results, output_file_name): 45 """Write the results of systrace to an HTML file. 46 47 Args: 48 trace_results: A list of TraceResults. 49 output_file_name: The name of the HTML file that the trace viewer 50 results should be written to. 51 """ 52 def _ReadAsset(src_dir, filename): 53 return open(os.path.join(src_dir, filename)).read() 54 55 # TODO(rnephew): The tracing output formatter is able to handle a single 56 # systrace trace just as well as it handles multiple traces. The obvious thing 57 # to do here would be to use it all for all systrace output: however, we want 58 # to continue using the legacy way of formatting systrace output when a single 59 # systrace and the tracing controller trace are present in order to match the 60 # Java verison of systrace. Java systrace is expected to be deleted at a later 61 # date. We should consolidate this logic when that happens. 62 63 if len(trace_results) > 3: 64 NewGenerateHTMLOutput(trace_results, output_file_name) 65 return os.path.abspath(output_file_name) 66 67 systrace_dir = os.path.abspath(os.path.dirname(__file__)) 68 69 try: 70 from systrace import update_systrace_trace_viewer 71 except ImportError: 72 pass 73 else: 74 update_systrace_trace_viewer.update() 75 76 trace_viewer_html = _ReadAsset(systrace_dir, 'systrace_trace_viewer.html') 77 78 # Open the file in binary mode to prevent python from changing the 79 # line endings, then write the prefix. 80 systrace_dir = os.path.abspath(os.path.dirname(__file__)) 81 html_prefix = _ReadAsset(systrace_dir, 'prefix.html') 82 html_suffix = _ReadAsset(systrace_dir, 'suffix.html') 83 trace_viewer_html = _ReadAsset(systrace_dir, 84 'systrace_trace_viewer.html') 85 86 # Open the file in binary mode to prevent python from changing the 87 # line endings, then write the prefix. 88 html_file = open(output_file_name, 'wb') 89 html_file.write(html_prefix.replace('{{SYSTRACE_TRACE_VIEWER_HTML}}', 90 trace_viewer_html)) 91 92 # Write the trace data itself. There is a separate section of the form 93 # <script class="trace-data" type="application/text"> ... </script> 94 # for each tracing agent (including the controller tracing agent). 95 html_file.write('<!-- BEGIN TRACE -->\n') 96 for result in trace_results: 97 html_file.write(' <script class="trace-data" type="application/text">\n') 98 html_file.write(_ConvertToHtmlString(result.raw_data)) 99 html_file.write(' </script>\n') 100 html_file.write('<!-- END TRACE -->\n') 101 102 # Write the suffix and finish. 103 html_file.write(html_suffix) 104 html_file.close() 105 106 final_path = os.path.abspath(output_file_name) 107 return final_path 108 109def _ConvertToHtmlString(result): 110 """Convert a trace result to the format to be output into HTML. 111 112 If the trace result is a dictionary or list, JSON-encode it. 113 If the trace result is a string, leave it unchanged. 114 """ 115 if isinstance(result, dict) or isinstance(result, list): 116 return json.dumps(result) 117 elif isinstance(result, str): 118 return result 119 else: 120 raise ValueError('Invalid trace result format for HTML output') 121 122def GenerateJSONOutput(trace_results, output_file_name): 123 """Write the results of systrace to a JSON file. 124 125 Args: 126 trace_results: A list of TraceResults. 127 output_file_name: The name of the JSON file that the trace viewer 128 results should be written to. 129 """ 130 results = _ConvertTraceListToDictionary(trace_results) 131 results[CONTROLLER_TRACE_DATA_KEY] = ( 132 tracing_controller.TRACE_DATA_CONTROLLER_NAME) 133 with open(output_file_name, 'w') as json_file: 134 json.dump(results, json_file) 135 final_path = os.path.abspath(output_file_name) 136 return final_path 137 138def MergeTraceResultsIfNeeded(trace_results): 139 """Merge a list of trace data, if possible. This function can take any list 140 of trace data, but it will only merge the JSON data (since that's all 141 we can merge). 142 143 Args: 144 trace_results: A list of TraceResults containing trace data. 145 """ 146 if len(trace_results) <= 1: 147 return trace_results 148 merge_candidates = [] 149 for result in trace_results: 150 # Try to detect a JSON file cheaply since that's all we can merge. 151 if result.raw_data[0] != '{': 152 continue 153 try: 154 json_data = json.loads(result.raw_data) 155 except ValueError: 156 continue 157 merge_candidates.append(trace_result.TraceResult(result.source_name, 158 json_data)) 159 160 if len(merge_candidates) <= 1: 161 return trace_results 162 163 other_results = [r for r in trace_results 164 if not r.source_name in 165 [c.source_name for c in merge_candidates]] 166 167 merged_data = merge_candidates[0].raw_data 168 169 for candidate in merge_candidates[1:]: 170 json_data = candidate.raw_data 171 for key, value in json_data.items(): 172 if not str(key) in merged_data or not merged_data[str(key)]: 173 merged_data[str(key)] = value 174 175 return ([trace_result.TraceResult('merged-data', json.dumps(merged_data))] 176 + other_results) 177 178def _EncodeTraceData(trace_string): 179 compressed_trace = StringIO.StringIO() 180 with gzip.GzipFile(fileobj=compressed_trace, mode='w') as f: 181 f.write(trace_string) 182 b64_content = base64.b64encode(compressed_trace.getvalue()) 183 return b64_content 184 185def _ConvertTraceListToDictionary(trace_list): 186 trace_dict = {} 187 for trace in trace_list: 188 trace_dict[trace.source_name] = trace.raw_data 189 return trace_dict 190