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