1#!/usr/bin/env python3 2# Copyright 2020 The Pigweed Authors 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may not 5# use this file except in compliance with the License. You may obtain a copy of 6# the License at 7# 8# https://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations under 14# the License. 15r""" 16 17Trace module which creates trace files from a list of trace events. 18 19This is a work in progress, future work will look to add: 20 - Config options to customize output. 21 - A method of providing custom data formatters. 22 - Perfetto support. 23""" 24from enum import Enum 25import json 26import logging 27import struct 28from typing import Iterable, NamedTuple 29 30_LOG = logging.getLogger('pw_trace') 31 32 33class TraceType(Enum): 34 INVALID = 0 35 INSTANTANEOUS = 1 36 INSTANTANEOUS_GROUP = 2 37 ASYNC_START = 3 38 ASYNC_STEP = 4 39 ASYNC_END = 5 40 DURATION_START = 6 41 DURATION_END = 7 42 DURATION_GROUP_START = 8 43 DURATION_GROUP_END = 9 44 45 # TODO(hepler): Remove these aliases for the original style-incompliant 46 # names when users have migrated. 47 DurationStart = 6 # pylint: disable=invalid-name 48 DurationEnd = 7 # pylint: disable=invalid-name 49 50 51class TraceEvent(NamedTuple): 52 event_type: TraceType 53 module: str 54 label: str 55 timestamp_us: int 56 group: str = "" 57 trace_id: int = 0 58 flags: int = 0 59 has_data: bool = False 60 data_fmt: str = "" 61 data: bytes = b'' 62 63 64def event_has_trace_id(event_type): 65 return event_type in { 66 "PW_TRACE_EVENT_TYPE_ASYNC_START", 67 "PW_TRACE_EVENT_TYPE_ASYNC_STEP", 68 "PW_TRACE_EVENT_TYPE_ASYNC_END", 69 } 70 71 72def generate_trace_json(events: Iterable[TraceEvent]): 73 """Generates a list of JSON lines from provided trace events.""" 74 json_lines = [] 75 for event in events: 76 if event.module is None or event.timestamp_us is None or \ 77 event.event_type is None or event.label is None: 78 _LOG.error("Invalid sample") 79 continue 80 81 line = { 82 "pid": event.module, 83 "name": (event.label), 84 "ts": event.timestamp_us 85 } 86 if event.event_type == TraceType.DURATION_START: 87 line["ph"] = "B" 88 line["tid"] = event.label 89 elif event.event_type == TraceType.DURATION_END: 90 line["ph"] = "E" 91 line["tid"] = event.label 92 elif event.event_type == TraceType.DURATION_GROUP_START: 93 line["ph"] = "B" 94 line["tid"] = event.group 95 elif event.event_type == TraceType.DURATION_GROUP_END: 96 line["ph"] = "E" 97 line["tid"] = event.group 98 elif event.event_type == TraceType.INSTANTANEOUS: 99 line["ph"] = "I" 100 line["s"] = "p" 101 elif event.event_type == TraceType.INSTANTANEOUS_GROUP: 102 line["ph"] = "I" 103 line["s"] = "t" 104 line["tid"] = event.group 105 elif event.event_type == TraceType.ASYNC_START: 106 line["ph"] = "b" 107 line["scope"] = event.group 108 line["tid"] = event.group 109 line["cat"] = event.module 110 line["id"] = event.trace_id 111 line["args"] = {"id": line["id"]} 112 elif event.event_type == TraceType.ASYNC_STEP: 113 line["ph"] = "n" 114 line["scope"] = event.group 115 line["tid"] = event.group 116 line["cat"] = event.module 117 line["id"] = event.trace_id 118 line["args"] = {"id": line["id"]} 119 elif event.event_type == TraceType.ASYNC_END: 120 line["ph"] = "e" 121 line["scope"] = event.group 122 line["tid"] = event.group 123 line["cat"] = event.module 124 line["id"] = event.trace_id 125 line["args"] = {"id": line["id"]} 126 else: 127 _LOG.error("Unknown event type, skipping") 128 continue 129 130 # Handle Data 131 if event.has_data: 132 if event.data_fmt == "@pw_arg_label": 133 line["name"] = event.data.decode("utf-8") 134 elif event.data_fmt == "@pw_arg_group": 135 line["tid"] = event.data.decode("utf-8") 136 elif event.data_fmt == "@pw_arg_counter": 137 line["ph"] = "C" 138 line["args"] = { 139 line["name"]: int.from_bytes(event.data, "little") 140 } 141 elif event.data_fmt.startswith("@pw_py_struct_fmt:"): 142 items = struct.unpack_from( 143 event.data_fmt[len("@pw_py_struct_fmt:"):], event.data) 144 args = {} 145 for i, item in enumerate(items): 146 args["data_" + str(i)] = item 147 line["args"] = args 148 else: 149 line["args"] = {"data": event.data.hex()} 150 151 # Encode as JSON 152 json_lines.append(json.dumps(line)) 153 154 return json_lines 155