1 /*
<lambda>null2  * Copyright (C) 2023 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 android.tools.traces.parsers.perfetto
18 
19 import android.tools.CrossPlatform
20 import android.tools.Timestamp
21 import android.tools.parsers.AbstractTraceParser
22 import android.tools.traces.surfaceflinger.Transaction
23 import android.tools.traces.surfaceflinger.TransactionsTrace
24 import android.tools.traces.surfaceflinger.TransactionsTraceEntry
25 import kotlin.math.min
26 
27 /** Parser for [TransactionsTrace] */
28 class TransactionsTraceParser :
29     AbstractTraceParser<
30         TraceProcessorSession, TransactionsTraceEntry, TransactionsTraceEntry, TransactionsTrace
31     >() {
32 
33     override val traceName = "Layers trace (SF)"
34 
35     override fun createTrace(entries: Collection<TransactionsTraceEntry>): TransactionsTrace {
36         return TransactionsTrace(entries)
37     }
38 
39     override fun doDecodeByteArray(bytes: ByteArray): TraceProcessorSession {
40         error("This parser can only read from perfetto trace processor")
41     }
42 
43     override fun shouldParseEntry(entry: TransactionsTraceEntry) = true
44 
45     override fun getEntries(input: TraceProcessorSession): List<TransactionsTraceEntry> {
46         val traceEntries = ArrayList<TransactionsTraceEntry>()
47 
48         val realToMonotonicTimeOffsetNs =
49             queryRealToMonotonicTimeOffsetNs(input, "surfaceflinger_transactions")
50         val entriesCount = queryTraceEntriesCount(input)
51 
52         for (startEntryId in 0L until entriesCount step BATCH_SIZE) {
53             val endEntryId = min(startEntryId + BATCH_SIZE, entriesCount)
54 
55             val batchRows = input.query(getSqlQueryTransactions(startEntryId, endEntryId)) { it }
56             val entryGroups = batchRows.groupBy { it.get("trace_entry_id") }
57 
58             for (entryId in startEntryId until endEntryId) {
59                 val rows = entryGroups[entryId]!!
60                 val entry = buildTraceEntry(rows, realToMonotonicTimeOffsetNs)
61                 traceEntries.add(entry)
62             }
63         }
64 
65         return traceEntries
66     }
67 
68     override fun getTimestamp(entry: TransactionsTraceEntry): Timestamp = entry.timestamp
69 
70     override fun onBeforeParse(input: TraceProcessorSession) {}
71 
72     override fun doParseEntry(entry: TransactionsTraceEntry) = entry
73 
74     companion object {
75         private const val BATCH_SIZE = 200L
76 
77         private fun queryTraceEntriesCount(session: TraceProcessorSession): Long {
78             val sql =
79                 """
80                 SELECT count(*) FROM surfaceflinger_transactions;
81             """
82                     .trimIndent()
83             return session.query(sql) { rows ->
84                 require(rows.size == 1) { "Expected one row with count of trace entries." }
85                 rows.get(0).get("count(*)") as Long
86             }
87         }
88 
89         private fun getSqlQueryTransactions(startEntryId: Long, endEntryId: Long): String {
90             return """
91                SELECT sft.id AS trace_entry_id, args.key, args.display_value AS value, args.value_type
92                FROM surfaceflinger_transactions AS sft
93                INNER JOIN args ON sft.arg_set_id = args.arg_set_id
94                WHERE trace_entry_id BETWEEN $startEntryId AND ${endEntryId - 1};
95             """
96                 .trimIndent()
97         }
98 
99         private fun buildTraceEntry(
100             rows: List<Row>,
101             realToMonotonicTimeOffsetNs: Long
102         ): TransactionsTraceEntry {
103             val args = Args.build(rows)
104             val transactions: Collection<Transaction> =
105                 args.getChildren("transactions")?.map { transaction ->
106                     Transaction(
107                         transaction.getChild("pid")?.getInt() ?: -1,
108                         transaction.getChild("uid")?.getInt() ?: -1,
109                         transaction.getChild("vsync_id")?.getLong() ?: -1,
110                         transaction.getChild("post_time")?.getLong() ?: -1,
111                         transaction.getChild("transaction_id")?.getLong() ?: -1,
112                         transaction.getChildren("merged_transaction_ids")?.map { it.getLong() }
113                             ?: emptyList()
114                     )
115                 }
116                     ?: emptyList()
117 
118             val traceEntry =
119                 TransactionsTraceEntry(
120                     CrossPlatform.timestamp.from(
121                         elapsedNanos = args.getChild("elapsed_realtime_nanos")?.getLong() ?: 0,
122                         elapsedOffsetNanos = realToMonotonicTimeOffsetNs
123                     ),
124                     args.getChild("vsync_id")?.getLong() ?: -1,
125                     transactions
126                 )
127 
128             transactions.forEach { it.appliedInEntry = traceEntry }
129 
130             return traceEntry
131         }
132     }
133 }
134