1 /*
2  * 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.events
18 
19 import android.tools.Timestamp
20 import android.tools.Timestamps
21 
22 /**
23  * Represents a CUJ Event from the [EventLog]
24  *
25  * {@inheritDoc}
26  */
27 class CujEvent(
28     timestamp: Timestamp,
29     val cuj: CujType,
30     processId: Int,
31     uid: String,
32     threadId: Int,
33     eventTag: String,
34     val cujTag: String?,
35 ) : Event(timestamp, processId, uid, threadId, eventTag) {
36 
37     val type: Type = eventTag.asCujType()
38 
toStringnull39     override fun toString(): String {
40         return "CujEvent(" +
41             "timestamp=$timestamp, " +
42             "cuj=$cuj, " +
43             "processId=$processId, " +
44             "uid=$uid, " +
45             "threadId=$threadId, " +
46             "eventTag=$eventTag" +
47             ")"
48     }
49 
50     companion object {
fromDatanull51         fun fromData(
52             processId: Int,
53             uid: String,
54             threadId: Int,
55             eventTag: String,
56             data: String
57         ): CujEvent {
58             return CujEvent(
59                 Timestamps.from(
60                     elapsedNanos = getElapsedTimestampFromData(data, eventTag.asCujType()),
61                     systemUptimeNanos = getSystemUptimeNanosFromData(data, eventTag.asCujType()),
62                     unixNanos = getUnixTimestampFromData(data, eventTag.asCujType())
63                 ),
64                 getCujMarkerFromData(data, eventTag.asCujType()),
65                 processId,
66                 uid,
67                 threadId,
68                 eventTag,
69                 getCujTagFromData(data, eventTag.asCujType())
70             )
71         }
72 
getCujMarkerFromDatanull73         private fun getCujMarkerFromData(data: String, cujType: Type): CujType {
74             val dataEntries = getDataEntries(data, cujType)
75             val eventId = dataEntries.first().toInt()
76             return CujType.from(eventId)
77         }
78 
getUnixTimestampFromDatanull79         private fun getUnixTimestampFromData(data: String, cujType: Type): Long {
80             val dataEntries = getDataEntries(data, cujType)
81             return dataEntries[1].toLong()
82         }
83 
getElapsedTimestampFromDatanull84         private fun getElapsedTimestampFromData(data: String, cujType: Type): Long {
85             val dataEntries = getDataEntries(data, cujType)
86             return dataEntries[2].toLong()
87         }
88 
getSystemUptimeNanosFromDatanull89         private fun getSystemUptimeNanosFromData(data: String, cujType: Type): Long {
90             val dataEntries = getDataEntries(data, cujType)
91             return dataEntries[3].toLong()
92         }
93 
getCujTagFromDatanull94         private fun getCujTagFromData(data: String, cujType: Type): String? {
95             val dataEntries = getDataEntries(data, cujType)
96             return when (cujType) {
97                 Type.START -> dataEntries[4]
98                 else -> null
99             }
100         }
101 
isNumericnull102         private fun isNumeric(toCheck: String): Boolean {
103             return try {
104                 toCheck.toLong().toString() == toCheck
105             } catch (e: NumberFormatException) {
106                 false
107             }
108         }
109 
getDataEntriesnull110         private fun getDataEntries(data: String, cujType: Type): List<String> {
111             when (cujType) {
112                 Type.START -> {
113                     // (CUJ Type|1|5),(Unix Time Ns|2|3),(Elapsed Time Ns|2|3),(Uptime Time Ns|2|3)
114                     val (cujType, unixNs, elapsedNs, uptimeNs, tag) =
115                         data.replace("[", "").replace("]", "").split(",")
116                     // Not using a Regex because it's not supported by Kotlin/Closure
117                     require(
118                         isNumeric(cujType) &&
119                             isNumeric(unixNs) &&
120                             isNumeric(elapsedNs) &&
121                             isNumeric(uptimeNs)
122                     ) {
123                         "Data \"$data\" didn't match expected format"
124                     }
125                 }
126                 else -> {
127                     // Not using a Regex because it's not supported by Kotlin/Closure
128                     val (cujType, unixNs, elapsedNs, uptimeNs) =
129                         data.replace("[", "").replace("]", "").split(",")
130                     require(
131                         isNumeric(cujType) &&
132                             isNumeric(unixNs) &&
133                             isNumeric(elapsedNs) &&
134                             isNumeric(uptimeNs)
135                     ) {
136                         "Data \"$data\" didn't match expected format"
137                     }
138                 }
139             }
140 
141             return data.slice(1..data.length - 2).split(",")
142         }
143 
144         enum class Type {
145             START,
146             END,
147             CANCEL
148         }
149 
150         const val JANK_CUJ_BEGIN_TAG = "jank_cuj_events_begin_request"
151         const val JANK_CUJ_END_TAG = "jank_cuj_events_end_request"
152         const val JANK_CUJ_CANCEL_TAG = "jank_cuj_events_cancel_request"
153 
Stringnull154         fun String.asCujType(): Type {
155             return when (this) {
156                 JANK_CUJ_BEGIN_TAG -> Type.START
157                 JANK_CUJ_END_TAG -> Type.END
158                 JANK_CUJ_CANCEL_TAG -> Type.CANCEL
159                 else -> error("Unhandled tag type")
160             }
161         }
162     }
163 }
164