• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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