xref: /aosp_15_r20/platform_testing/libraries/flicker/utils/src/android/tools/traces/monitors/TraceMonitor.kt (revision dd0948b35e70be4c0246aabd6c72554a5eb8b22a)
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.function.Supplier
21 import android.tools.io.TraceType
22 import android.tools.traces.io.IResultData
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 
37     protected abstract fun doStart()
38 
39     protected abstract fun doStop(): File
40 
41     protected open fun doStopTraces(): Map<TraceType, File> = mapOf(traceType to doStop())
42 
43     final override fun start() {
44         android.tools.withTracing("${this::class.simpleName}#start") {
45             validateStart()
46             doStart()
47         }
48     }
49 
50     open fun validateStart() {
51         if (this.isEnabled) {
52             throw UnsupportedOperationException(
53                 "${traceType.name} trace already running. " +
54                     "This is likely due to chained 'withTracing' calls."
55             )
56         }
57     }
58 
59     /** Stops monitor. */
60     override fun stop(writer: ResultWriter) {
61         val artifacts =
62             try {
63                 android.tools.withTracing("${this::class.simpleName}#stop") {
64                     doStopTraces()
65                         .map { (key, value) -> key to moveTraceFileToTmpDir(value) }
66                         .toMap()
67                 }
68             } catch (e: Throwable) {
69                 throw RuntimeException(
70                     "Could not stop ${traceType.name} trace and save it to ${traceType.fileName}",
71                     e,
72                 )
73             }
74         artifacts.forEach { (key, value) -> writer.addTraceResult(key, value) }
75     }
76 
77     private fun moveTraceFileToTmpDir(sourceFile: File): File {
78         val newFile = File.createTempFile(sourceFile.name, "")
79         IoUtils.moveFile(sourceFile, newFile)
80         require(newFile.exists()) { "Unable to save trace file $newFile" }
81         return newFile
82     }
83 
84     /**
85      * Uses [writer] to write the trace generated by executing the commands defined by [predicate].
86      *
87      * @param writer Write to use to write the collected traces
88      * @param predicate Commands to execute
89      * @throws UnsupportedOperationException If tracing is already activated
90      */
91     fun withTracing(writer: ResultWriter, predicate: Runnable) {
92         android.tools.withTracing("${this::class.simpleName}#withTracing") {
93             try {
94                 this.start()
95                 predicate.run()
96             } finally {
97                 this.stop(writer)
98             }
99         }
100     }
101 
102     /**
103      * Acquires the trace generated when executing the commands defined in the [predicate].
104      *
105      * @param predicate Commands to execute
106      * @param resultReaderProvider Predicate to generate new result readers
107      * @param
108      * @throws UnsupportedOperationException If tracing is already activated
109      */
110     fun withTracing(
111         resultReaderProvider: Supplier<IResultData, ResultReader>,
112         predicate: Runnable,
113     ): ResultReader {
114         val writer = createWriter()
115         withTracing(writer, predicate)
116         val result = writer.write()
117         return resultReaderProvider.get(result)
118     }
119 
120     private fun createWriter(): ResultWriter {
121         val className = this::class.simpleName ?: error("Missing class name for $this")
122         val scenario = ScenarioBuilder().forClass(className).build()
123         val tmpDir = createTempDirectory("withTracing").toFile()
124         return ResultWriter().forScenario(scenario).withOutputDir(tmpDir)
125     }
126 
127     companion object {
128         @JvmStatic protected val TRACE_DIR = File("/data/misc/wmtrace/")
129     }
130 }
131