blob: c3f39a515a5f900d4fc0503477ce13f764da4093 [file] [log] [blame]
/*
* 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.io
import android.tools.common.CrossPlatform
import android.tools.common.Tag
import android.tools.common.Timestamp
import android.tools.common.io.BUFFER_SIZE
import android.tools.common.io.FLICKER_IO_TAG
import android.tools.common.io.IReader
import android.tools.common.io.ResultArtifactDescriptor
import android.tools.common.io.TraceType
import android.tools.common.parsers.events.EventLogParser
import android.tools.common.traces.events.CujTrace
import android.tools.common.traces.events.EventLog
import android.tools.common.traces.surfaceflinger.LayersTrace
import android.tools.common.traces.surfaceflinger.TransactionsTrace
import android.tools.common.traces.wm.TransitionsTrace
import android.tools.common.traces.wm.WindowManagerTrace
import android.tools.device.traces.TraceConfig
import android.tools.device.traces.TraceConfigs
import android.tools.device.traces.parsers.surfaceflinger.LayersTraceParser
import android.tools.device.traces.parsers.surfaceflinger.TransactionsTraceParser
import android.tools.device.traces.parsers.wm.TransitionsTraceParser
import android.tools.device.traces.parsers.wm.WindowManagerDumpParser
import android.tools.device.traces.parsers.wm.WindowManagerTraceParser
import androidx.annotation.VisibleForTesting
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
/**
* Helper class to read results from a flicker artifact
*
* @param result to read from
* @param traceConfig
*/
open class ResultReader(internal var result: IResultData, internal val traceConfig: TraceConfigs) :
IReader {
override val artifactPath: String
get() = result.artifact.absolutePath
override val runStatus
get() = result.runStatus
internal val transitionTimeRange
get() = result.transitionTimeRange
override val isFailure
get() = runStatus.isFailure
override val executionError
get() = result.executionError
private fun withZipFile(predicate: (ZipInputStream) -> Unit) {
val zipInputStream =
ZipInputStream(
BufferedInputStream(ByteArrayInputStream(result.getArtifactBytes()), BUFFER_SIZE)
)
try {
predicate(zipInputStream)
} finally {
zipInputStream.closeEntry()
zipInputStream.close()
}
}
private fun forEachFileInZip(predicate: (ZipEntry) -> Unit) {
withZipFile {
var zipEntry: ZipEntry? = it.nextEntry
while (zipEntry != null) {
predicate(zipEntry)
zipEntry = it.nextEntry
}
}
}
@Throws(IOException::class)
private fun readFromZip(descriptor: ResultArtifactDescriptor): ByteArray? {
CrossPlatform.log.d(FLICKER_IO_TAG, "Reading descriptor=$descriptor from $result")
var foundFile = false
val outByteArray = ByteArrayOutputStream()
val tmpBuffer = ByteArray(BUFFER_SIZE)
withZipFile {
var zipEntry: ZipEntry? = it.nextEntry
while (zipEntry != null) {
if (zipEntry.name == descriptor.fileNameInArtifact) {
val outputStream = BufferedOutputStream(outByteArray, BUFFER_SIZE)
try {
var size = it.read(tmpBuffer, 0, BUFFER_SIZE)
while (size > 0) {
outputStream.write(tmpBuffer, 0, size)
size = it.read(tmpBuffer, 0, BUFFER_SIZE)
}
it.closeEntry()
} finally {
outputStream.flush()
outputStream.close()
}
foundFile = true
break
}
zipEntry = it.nextEntry
}
}
return if (foundFile) outByteArray.toByteArray() else null
}
override fun readBytes(traceType: TraceType, tag: String): ByteArray? =
readFromZip(ResultArtifactDescriptor(traceType, tag))
/**
* {@inheritDoc}
* @throws IOException if the artifact file doesn't exist or can't be read
*/
@Throws(IOException::class)
override fun readWmState(tag: String): WindowManagerTrace? {
val descriptor = ResultArtifactDescriptor(TraceType.WM_DUMP, tag)
CrossPlatform.log.d(FLICKER_IO_TAG, "Reading WM trace descriptor=$descriptor from $result")
val traceData = readFromZip(descriptor)
return traceData?.let { WindowManagerDumpParser().parse(it, clearCache = true) }
}
/**
* {@inheritDoc}
* @throws IOException if the artifact file doesn't exist or can't be read
*/
@Throws(IOException::class)
override fun readWmTrace(): WindowManagerTrace? {
val descriptor = ResultArtifactDescriptor(TraceType.WM)
return readFromZip(descriptor)?.let {
val trace =
WindowManagerTraceParser()
.parse(
it,
from = transitionTimeRange.start,
to = transitionTimeRange.end,
addInitialEntry = true,
clearCache = true
)
val minimumEntries = minimumTraceEntriesForConfig(traceConfig.wmTrace)
require(trace.entries.size >= minimumEntries) {
"WM trace contained ${trace.entries.size} entries, " +
"expected at least $minimumEntries... :: " +
"transition starts at ${transitionTimeRange.start} and " +
"ends at ${transitionTimeRange.end}."
}
trace
}
}
/**
* {@inheritDoc}
* @throws IOException if the artifact file doesn't exist or can't be read
*/
@Throws(IOException::class)
override fun readLayersTrace(): LayersTrace? {
val descriptor = ResultArtifactDescriptor(TraceType.SF)
return readFromZip(descriptor)?.let {
val trace =
LayersTraceParser()
.parse(
it,
transitionTimeRange.start,
transitionTimeRange.end,
addInitialEntry = true,
clearCache = true
)
val minimumEntries = minimumTraceEntriesForConfig(traceConfig.layersTrace)
require(trace.entries.size >= minimumEntries) {
"Layers trace contained ${trace.entries.size} entries, " +
"expected at least $minimumEntries... :: " +
"transition starts at ${transitionTimeRange.start} and " +
"ends at ${transitionTimeRange.end}."
}
trace
}
}
/**
* {@inheritDoc}
* @throws IOException if the artifact file doesn't exist or can't be read
*/
@Throws(IOException::class)
override fun readLayersDump(tag: String): LayersTrace? {
val descriptor = ResultArtifactDescriptor(TraceType.SF_DUMP, tag)
val traceData = readFromZip(descriptor)
return traceData?.let { LayersTraceParser().parse(it, clearCache = true) }
}
/**
* {@inheritDoc}
* @throws IOException if the artifact file doesn't exist or can't be read
*/
@Throws(IOException::class)
override fun readTransactionsTrace(): TransactionsTrace? =
doReadTransactionsTrace(from = transitionTimeRange.start, to = transitionTimeRange.end)
private fun doReadTransactionsTrace(from: Timestamp, to: Timestamp): TransactionsTrace? {
val traceData = readFromZip(ResultArtifactDescriptor(TraceType.TRANSACTION))
return traceData?.let {
val trace = TransactionsTraceParser().parse(it, from, to, addInitialEntry = true)
require(trace.entries.isNotEmpty()) { "Transactions trace cannot be empty" }
trace
}
}
/**
* {@inheritDoc}
* @throws IOException if the artifact file doesn't exist or can't be read
*/
@Throws(IOException::class)
override fun readTransitionsTrace(): TransitionsTrace? {
val traceData = readFromZip(ResultArtifactDescriptor(TraceType.TRANSITION)) ?: return null
val fullTrace = TransitionsTraceParser().parse(traceData)
val trace = fullTrace.slice(transitionTimeRange.start, transitionTimeRange.end)
if (!traceConfig.transitionsTrace.allowNoChange) {
require(trace.entries.isNotEmpty()) { "Transitions trace cannot be empty" }
}
return trace
}
private fun minimumTraceEntriesForConfig(config: TraceConfig): Int {
return if (config.allowNoChange) 1 else 2
}
/**
* {@inheritDoc}
* @throws IOException if the artifact file doesn't exist or can't be read
*/
@Throws(IOException::class)
override fun readEventLogTrace(): EventLog? {
val descriptor = ResultArtifactDescriptor(TraceType.EVENT_LOG)
return readFromZip(descriptor)?.let {
EventLogParser()
.parse(it, from = transitionTimeRange.start, to = transitionTimeRange.end)
}
}
/**
* {@inheritDoc}
* @throws IOException if the artifact file doesn't exist or can't be read
*/
@Throws(IOException::class)
override fun readCujTrace(): CujTrace? = readEventLogTrace()?.cujTrace
/** @return an [IReader] for the subsection of the trace we are reading in this reader */
override fun slice(startTimestamp: Timestamp, endTimestamp: Timestamp): ResultReader {
val slicedResult = result.slice(startTimestamp, endTimestamp)
return ResultReader(slicedResult, traceConfig)
}
override fun toString(): String = "$result"
/** @return the number of files in the artifact */
@VisibleForTesting
fun countFiles(): Int {
var count = 0
forEachFileInZip { count++ }
return count
}
/** @return if a file with type [traceType] linked to a [tag] exists in the artifact */
fun hasTraceFile(traceType: TraceType, tag: String = Tag.ALL): Boolean {
val descriptor = ResultArtifactDescriptor(traceType, tag)
var found = false
forEachFileInZip { found = found || (it.name == descriptor.fileNameInArtifact) }
return found
}
}