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 15 16 17# TODO(alexandermont): Current version of trace viewer does not support 18# the controller tracing agent output. Thus we use this variable to 19# suppress this tracing agent's output. This should be removed once 20# trace viewer is working again. 21OUTPUT_CONTROLLER_TRACE_ = False 22CONTROLLER_TRACE_DATA_KEY = 'controllerTraceDataKey' 23 24 25def GenerateHTMLOutput(trace_results, output_file_name): 26 """Write the results of systrace to an HTML file. 27 28 Args: 29 trace_results: A list of TraceResults. 30 output_file_name: The name of the HTML file that the trace viewer 31 results should be written to. 32 """ 33 def _ReadAsset(src_dir, filename): 34 return open(os.path.join(src_dir, filename)).read() 35 36 systrace_dir = os.path.abspath(os.path.dirname(__file__)) 37 38 try: 39 from systrace import update_systrace_trace_viewer 40 except ImportError: 41 pass 42 else: 43 update_systrace_trace_viewer.update() 44 45 trace_viewer_html = _ReadAsset(systrace_dir, 'systrace_trace_viewer.html') 46 47 # Open the file in binary mode to prevent python from changing the 48 # line endings, then write the prefix. 49 systrace_dir = os.path.abspath(os.path.dirname(__file__)) 50 html_prefix = _ReadAsset(systrace_dir, 'prefix.html') 51 html_suffix = _ReadAsset(systrace_dir, 'suffix.html') 52 trace_viewer_html = _ReadAsset(systrace_dir, 53 'systrace_trace_viewer.html') 54 55 # Open the file in binary mode to prevent python from changing the 56 # line endings, then write the prefix. 57 html_file = open(output_file_name, 'wb') 58 html_file.write(html_prefix.replace('{{SYSTRACE_TRACE_VIEWER_HTML}}', 59 trace_viewer_html)) 60 61 # Write the trace data itself. There is a separate section of the form 62 # <script class="trace-data" type="application/text"> ... </script> 63 # for each tracing agent (including the controller tracing agent). 64 html_file.write('<!-- BEGIN TRACE -->\n') 65 for result in trace_results: 66 if (result.source_name == tracing_controller.TRACE_DATA_CONTROLLER_NAME and 67 not OUTPUT_CONTROLLER_TRACE_): 68 continue 69 html_file.write(' <script class="trace-data" type="application/text">\n') 70 html_file.write(_ConvertToHtmlString(result.raw_data)) 71 html_file.write(' </script>\n') 72 html_file.write('<!-- END TRACE -->\n') 73 74 # Write the suffix and finish. 75 html_file.write(html_suffix) 76 html_file.close() 77 78 final_path = os.path.abspath(output_file_name) 79 return final_path 80 81def _ConvertToHtmlString(result): 82 """Convert a trace result to the format to be output into HTML. 83 84 If the trace result is a dictionary or list, JSON-encode it. 85 If the trace result is a string, leave it unchanged. 86 """ 87 if isinstance(result, dict) or isinstance(result, list): 88 return json.dumps(result) 89 elif isinstance(result, str): 90 return result 91 else: 92 raise ValueError('Invalid trace result format for HTML output') 93 94def GenerateJSONOutput(trace_results, output_file_name): 95 """Write the results of systrace to a JSON file. 96 97 Args: 98 trace_results: A list of TraceResults. 99 output_file_name: The name of the JSON file that the trace viewer 100 results should be written to. 101 """ 102 results = _ConvertTraceListToDictionary(trace_results) 103 results[CONTROLLER_TRACE_DATA_KEY] = ( 104 tracing_controller.TRACE_DATA_CONTROLLER_NAME) 105 if not OUTPUT_CONTROLLER_TRACE_: 106 results[tracing_controller.TRACE_DATA_CONTROLLER_NAME] = [] 107 with open(output_file_name, 'w') as json_file: 108 json.dump(results, json_file) 109 final_path = os.path.abspath(output_file_name) 110 return final_path 111 112def MergeTraceResultsIfNeeded(trace_results): 113 """Merge a list of trace data, if possible. This function can take any list 114 of trace data, but it will only merge the JSON data (since that's all 115 we can merge). 116 117 Args: 118 trace_results: A list of TraceResults containing trace data. 119 """ 120 if len(trace_results) <= 1: 121 return trace_results 122 merge_candidates = [] 123 for result in trace_results: 124 # Try to detect a JSON file cheaply since that's all we can merge. 125 if result.raw_data[0] != '{': 126 continue 127 try: 128 json_data = json.loads(result.raw_data) 129 except ValueError: 130 continue 131 merge_candidates.append(trace_result.TraceResult(result.source_name, 132 json_data)) 133 134 if len(merge_candidates) <= 1: 135 return trace_results 136 137 other_results = [r for r in trace_results 138 if not r.source_name in 139 [c.source_name for c in merge_candidates]] 140 141 merged_data = merge_candidates[0].raw_data 142 143 for candidate in merge_candidates[1:]: 144 json_data = candidate.raw_data 145 for key, value in json_data.items(): 146 if not str(key) in merged_data or not merged_data[str(key)]: 147 merged_data[str(key)] = value 148 149 return ([trace_result.TraceResult('merged-data', json.dumps(merged_data))] 150 + other_results) 151 152def _EncodeTraceData(trace_string): 153 compressed_trace = StringIO.StringIO() 154 with gzip.GzipFile(fileobj=compressed_trace, mode='w') as f: 155 f.write(trace_string) 156 b64_content = base64.b64encode(compressed_trace.getvalue()) 157 return b64_content 158 159def _ConvertTraceListToDictionary(trace_list): 160 trace_dict = {} 161 for trace in trace_list: 162 trace_dict[trace.source_name] = trace.raw_data 163 return trace_dict 164