#!/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, } _SYSTRACE_HEADER = 'Systrace' def NewGenerateHTMLOutput(trace_results, output_file_name): trace_data_builder = trace_data.TraceDataBuilder() for trace in trace_results: trace_data_part = _SYSTRACE_TO_TRACE_DATA_NAME_MAPPING.get( trace.source_name) trace_data_builder.AddTraceFor(trace_data_part, trace.raw_data) trace_data_builder.AsData().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) > 3: 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') html_suffix = _ReadAsset(systrace_dir, 'suffix.html') 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. html_file = open(output_file_name, 'wb') html_file.write(html_prefix.replace('{{SYSTRACE_TRACE_VIEWER_HTML}}', trace_viewer_html)) # Write the trace data itself. There is a separate section of the form # # for each tracing agent (including the controller tracing agent). html_file.write('\n') for result in trace_results: html_file.write(' \n') html_file.write('\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