| /* |
| * Copyright (C) 2023 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.tools.device.traces.parsers.perfetto |
| |
| import android.tools.common.CrossPlatform |
| import android.tools.common.Timestamp |
| import android.tools.common.parsers.AbstractTraceParser |
| import android.tools.common.traces.surfaceflinger.Transaction |
| import android.tools.common.traces.surfaceflinger.TransactionsTrace |
| import android.tools.common.traces.surfaceflinger.TransactionsTraceEntry |
| import kotlin.math.min |
| |
| /** Parser for [TransactionsTrace] */ |
| class TransactionsTraceParser : |
| AbstractTraceParser< |
| TraceProcessorSession, TransactionsTraceEntry, TransactionsTraceEntry, TransactionsTrace |
| >() { |
| |
| override val traceName = "Layers trace (SF)" |
| |
| override fun createTrace(entries: List<TransactionsTraceEntry>): TransactionsTrace { |
| return TransactionsTrace(entries.toTypedArray()) |
| } |
| |
| override fun doDecodeByteArray(bytes: ByteArray): TraceProcessorSession { |
| error("This parser can only read from perfetto trace processor") |
| } |
| |
| override fun shouldParseEntry(entry: TransactionsTraceEntry) = true |
| |
| override fun getEntries(session: TraceProcessorSession): List<TransactionsTraceEntry> { |
| val traceEntries = ArrayList<TransactionsTraceEntry>() |
| |
| val realToMonotonicTimeOffsetNs = |
| queryRealToMonotonicTimeOffsetNs(session, "surfaceflinger_transactions") |
| val entriesCount = queryTraceEntriesCount(session) |
| |
| for (startEntryId in 0L..(entriesCount - 1) step BATCH_SIZE) { |
| val endEntryId = min(startEntryId + BATCH_SIZE, entriesCount) |
| |
| val batchRows = session.query(getSqlQueryTransactions(startEntryId, endEntryId)) { it } |
| val entryGroups = batchRows.groupBy { it.get("trace_entry_id") } |
| |
| for (entryId in startEntryId..(endEntryId - 1)) { |
| val rows = entryGroups[entryId]!! |
| val entry = buildTraceEntry(rows, realToMonotonicTimeOffsetNs) |
| traceEntries.add(entry) |
| } |
| } |
| |
| return traceEntries |
| } |
| |
| override fun getTimestamp(entry: TransactionsTraceEntry): Timestamp = entry.timestamp |
| |
| override fun onBeforeParse(input: TraceProcessorSession) {} |
| |
| override fun doParseEntry(entry: TransactionsTraceEntry) = entry |
| |
| companion object { |
| private const val BATCH_SIZE = 200L |
| |
| private fun queryTraceEntriesCount(session: TraceProcessorSession): Long { |
| val sql = |
| """ |
| SELECT count(*) FROM surfaceflinger_transactions; |
| """ |
| .trimIndent() |
| return session.query(sql) { rows -> |
| require(rows.size == 1) { "Expected one row with count of trace entries." } |
| rows.get(0).get("count(*)") as Long |
| } |
| } |
| |
| private fun getSqlQueryTransactions(startEntryId: Long, endEntryId: Long): String { |
| return """ |
| SELECT sft.id AS trace_entry_id, args.key, args.display_value AS value, args.value_type |
| FROM surfaceflinger_transactions AS sft |
| INNER JOIN args ON sft.arg_set_id = args.arg_set_id |
| WHERE trace_entry_id BETWEEN $startEntryId AND ${endEntryId - 1}; |
| """ |
| .trimIndent() |
| } |
| |
| private fun buildTraceEntry( |
| rows: List<Row>, |
| realToMonotonicTimeOffsetNs: Long |
| ): TransactionsTraceEntry { |
| val args = Args.build(rows) |
| val transactions: Array<Transaction> = |
| args |
| .getChildren("transactions") |
| ?.map { transaction -> |
| Transaction( |
| transaction.getChild("pid")?.getInt() ?: -1, |
| transaction.getChild("uid")?.getInt() ?: -1, |
| transaction.getChild("vsync_id")?.getLong() ?: -1, |
| transaction.getChild("post_time")?.getLong() ?: -1, |
| transaction.getChild("transaction_id")?.getLong() ?: -1, |
| transaction |
| .getChildren("merged_transaction_ids") |
| ?.map { it.getLong() } |
| ?.toTypedArray() |
| ?: arrayOf() |
| ) |
| } |
| ?.toTypedArray() |
| ?: arrayOf() |
| |
| val traceEntry = |
| TransactionsTraceEntry( |
| CrossPlatform.timestamp.from( |
| elapsedNanos = args.getChild("elapsed_realtime_nanos")?.getLong() ?: 0, |
| elapsedOffsetNanos = realToMonotonicTimeOffsetNs |
| ), |
| args.getChild("vsync_id")?.getLong() ?: -1, |
| transactions |
| ) |
| |
| transactions.forEach { it.appliedInEntry = traceEntry } |
| |
| return traceEntry |
| } |
| } |
| } |