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.monitors
18 
19 import android.tools.ScenarioBuilder
20 import android.tools.Tag
21 import android.tools.io.TraceType
22 import android.tools.traces.TRACE_CONFIG_REQUIRE_CHANGES
23 import android.tools.traces.io.IoUtils
24 import android.tools.traces.io.ResultReader
25 import android.tools.traces.io.ResultWriter
26 import java.io.File
27 import kotlin.io.path.createTempDirectory
28 
29 /**
30  * Base class for monitors containing common logic to read the trace as a byte array and save the
31  * trace to another location.
32  */
33 abstract class TraceMonitor : ITransitionMonitor {
34     abstract val isEnabled: Boolean
35     abstract val traceType: TraceType
36     protected abstract fun doStart()
37     protected abstract fun doStop(): File
38 
39     protected open fun doStopTraces(): Map<TraceType, File> = mapOf(traceType to doStop())
40 
41     final override fun start() {
42         android.tools.withTracing("${this::class.simpleName}#start") {
43             validateStart()
44             doStart()
45         }
46     }
47 
48     open fun validateStart() {
49         if (this.isEnabled) {
50             throw UnsupportedOperationException(
51                 "${traceType.name} trace already running. " +
52                     "This is likely due to chained 'withTracing' calls."
53             )
54         }
55     }
56 
57     /** Stops monitor. */
58     override fun stop(writer: ResultWriter) {
59         val artifacts =
60             try {
61                 android.tools.withTracing("${this::class.simpleName}#stop") {
62                     doStopTraces()
63                         .map { (key, value) -> key to moveTraceFileToTmpDir(value) }
64                         .toMap()
65                 }
66             } catch (e: Throwable) {
67                 throw RuntimeException(
68                     "Could not stop ${traceType.name} trace and save it to ${traceType.fileName}",
69                     e
70                 )
71             }
72         artifacts.forEach { (key, value) -> writer.addTraceResult(key, value) }
73     }
74 
75     private fun moveTraceFileToTmpDir(sourceFile: File): File {
76         val newFile = File.createTempFile(sourceFile.name, "")
77         IoUtils.moveFile(sourceFile, newFile)
78         require(newFile.exists()) { "Unable to save trace file $newFile" }
79         return newFile
80     }
81 
82     /**
83      * Uses [writer] to write the trace generated by executing the commands defined by [predicate].
84      *
85      * @param writer Write to use to write the collected traces
86      * @param predicate Commands to execute
87      * @throws UnsupportedOperationException If tracing is already activated
88      */
89     fun withTracing(writer: ResultWriter, predicate: () -> Unit) {
90         android.tools.withTracing("${this::class.simpleName}#withTracing") {
91             try {
92                 this.start()
93                 predicate()
94             } finally {
95                 this.stop(writer)
96             }
97         }
98     }
99 
100     /**
101      * Acquires the trace generated when executing the commands defined in the [predicate].
102      *
103      * @param predicate Commands to execute
104      * @throws UnsupportedOperationException If tracing is already activated
105      */
106     fun withTracing(tag: String = Tag.ALL, predicate: () -> Unit): ByteArray {
107         val writer = createWriter()
108         withTracing(writer, predicate)
109         val result = writer.write()
110         val reader = ResultReader(result, TRACE_CONFIG_REQUIRE_CHANGES)
111         val bytes = reader.readBytes(traceType, tag) ?: error("Missing trace $traceType")
112         result.artifact.deleteIfExists()
113         return bytes
114     }
115 
116     private fun createWriter(): ResultWriter {
117         val className = this::class.simpleName ?: error("Missing class name for $this")
118         val scenario = ScenarioBuilder().forClass(className).build()
119         val tmpDir = createTempDirectory("withTracing").toFile()
120         return ResultWriter().forScenario(scenario).withOutputDir(tmpDir)
121     }
122 
123     companion object {
124         @JvmStatic protected val TRACE_DIR = File("/data/misc/wmtrace/")
125     }
126 }
127