| #!/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, |
| 'powerTraceAsString': trace_data.BATTOR_TRACE_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 |
| # <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 |