1 /*
2  * Copyright 2025 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.tracing.driver.wire
18 
19 import androidx.tracing.driver.INVALID_LONG
20 import androidx.tracing.driver.TRACK_DESCRIPTOR_TYPE_COUNTER
21 import androidx.tracing.driver.TRACK_DESCRIPTOR_TYPE_PROCESS
22 import androidx.tracing.driver.TRACK_DESCRIPTOR_TYPE_THREAD
23 import androidx.tracing.driver.TraceEvent
24 import com.squareup.wire.ProtoWriter
25 import perfetto.protos.MutableCounterDescriptor
26 import perfetto.protos.MutableProcessDescriptor
27 import perfetto.protos.MutableThreadDescriptor
28 import perfetto.protos.MutableTracePacket
29 import perfetto.protos.MutableTrackDescriptor
30 import perfetto.protos.MutableTrackEvent
31 
32 /**
33  * Optimized serializer of [androidx.tracing.driver.TraceEvent], which writes out binary Perfetto
34  * trace_packet.proto with minimal allocations
35  *
36  * Internally uses mutable protos to avoid allocations / GC churn.
37  */
38 internal class WireTraceEventSerializer(sequenceId: Int, val protoWriter: ProtoWriter) {
39     /**
40      * Private scratchpad packet, used to avoid allocating a packet for each one serialized
41      *
42      * Always has the same track_event set on it
43      */
44     private val scratchTracePacket =
45         MutableTracePacket(timestamp = INVALID_LONG, trusted_packet_sequence_id = sequenceId)
46     /**
47      * Private scratchpad descriptor, used to avoid allocating a descriptor for each new track
48      * created
49      */
50     private val scratchTrackDescriptor = MutableTrackDescriptor()
51 
52     private val scratchTrackEvent = MutableTrackEvent(track_uuid = INVALID_LONG)
53 
writeTraceEventnull54     fun writeTraceEvent(event: TraceEvent) {
55         updateScratchPacketFromTraceEvent(
56             event,
57             scratchTracePacket,
58             scratchTrackDescriptor,
59             scratchTrackEvent
60         )
61         MutableTracePacket.Companion.ADAPTER.encodeWithTag(protoWriter, 1, scratchTracePacket)
62     }
63 
64     companion object {
65         /**
66          * Update the data in [MutableTracePacket] to represent the [TraceEvent] passed in.
67          *
68          * While it would be more elegant to have a MutableTracePacket extension constructor that
69          * takes a TraceEvent, that would cause large amounts of object churn.
70          */
71         @JvmStatic
updateScratchPacketFromTraceEventnull72         internal fun updateScratchPacketFromTraceEvent(
73             event: TraceEvent,
74             scratchTracePacket: MutableTracePacket,
75             scratchTrackDescriptor: MutableTrackDescriptor,
76             scratchTrackEvent: MutableTrackEvent,
77         ) {
78 
79             scratchTracePacket.timestamp = event.timestamp
80 
81             // in the common case when the track_descriptor isn't needed, clear it on the
82             // MutableTracePacket
83             scratchTracePacket.track_event = null
84             scratchTracePacket.track_descriptor = null
85             if (event.trackDescriptor != null) {
86                 // If the track_descriptor is needed, update and use the scratchTrackDescriptor to
87                 // avoid the
88                 // need to allocate a new object. Theoretically, this could be extended to the
89                 // counter/process/thread descriptors eventually if desired.
90                 event.trackDescriptor?.apply {
91                     scratchTrackDescriptor.thread = null
92                     scratchTrackDescriptor.counter = null
93                     scratchTrackDescriptor.process = null
94 
95                     when (val type = event.trackDescriptor!!.type) {
96                         TRACK_DESCRIPTOR_TYPE_COUNTER -> {
97                             scratchTrackDescriptor.name = name
98                             scratchTrackDescriptor.uuid = uuid
99                             scratchTrackDescriptor.parent_uuid = parentUuid
100                             scratchTrackDescriptor.counter = MutableCounterDescriptor()
101                         }
102                         TRACK_DESCRIPTOR_TYPE_PROCESS -> {
103                             scratchTrackDescriptor.name = null
104                             scratchTrackDescriptor.uuid = uuid
105                             scratchTrackDescriptor.process =
106                                 MutableProcessDescriptor(pid = pid, process_name = name)
107                         }
108                         TRACK_DESCRIPTOR_TYPE_THREAD -> {
109                             scratchTrackDescriptor.name = null
110                             scratchTrackDescriptor.uuid = uuid
111                             scratchTrackDescriptor.thread =
112                                 MutableThreadDescriptor(pid = pid, tid = tid, thread_name = name)
113                         }
114                         else -> throw IllegalStateException("Unknown TrackDescriptor type $type")
115                     }
116                     scratchTracePacket.track_descriptor = scratchTrackDescriptor
117                 }
118             } else {
119                 // If the track event is needed (that is, when track descriptor isn't present)
120                 // populate and use the scratch track event
121                 scratchTrackEvent.type = MutableTrackEvent.Type.fromValue(event.type)
122                 scratchTrackEvent.track_uuid = event.trackUuid
123                 scratchTrackEvent.name = event.name
124                 scratchTrackEvent.counter_value = event.counterLongValue
125                 scratchTrackEvent.double_counter_value = event.counterDoubleValue
126 
127                 // While it would be simpler to simply always set this.flow_ids, we avoid it in the
128                 // common cases when it does need to be called, since it's already up to date, as
129                 // Wire will deep copy the list with `immutableCopyOf(...)`. This is only necessary
130                 // if either it was already non-empty, or if it's becoming non-empty
131                 if (scratchTrackEvent.flow_ids.isNotEmpty() || event.flowIds.isNotEmpty()) {
132                     scratchTrackEvent.flow_ids = event.flowIds
133                 }
134                 scratchTracePacket.track_event = scratchTrackEvent
135             }
136         }
137     }
138 }
139