• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.common
18 
19 import kotlin.js.JsExport
20 import kotlin.js.JsName
21 import kotlin.math.max
22 
23 /**
24  * Time interface with all available timestamp types
25  *
26  * @param elapsedNanos Nanoseconds since boot, including time spent in sleep.
27  * @param systemUptimeNanos Nanoseconds since boot, not counting time spent in deep sleep
28  * @param unixNanos Nanoseconds since Unix epoch
29  */
30 @JsExport
31 data class Timestamp
32 internal constructor(
33     val elapsedNanos: Long = 0L,
34     val systemUptimeNanos: Long = 0L,
35     val unixNanos: Long = 0L,
36     private val realTimestampFormatter: (Long) -> String
37 ) : Comparable<Timestamp> {
38     val hasElapsedTimestamp = elapsedNanos != 0L
39     val hasSystemUptimeTimestamp = systemUptimeNanos != 0L
40     val hasUnixTimestamp = unixNanos != 0L
41     val isEmpty = !hasElapsedTimestamp && !hasSystemUptimeTimestamp && !hasUnixTimestamp
42     val hasAllTimestamps = hasUnixTimestamp && hasSystemUptimeTimestamp && hasElapsedTimestamp
43     @JsName("isMin") val isMin = elapsedNanos == 1L && systemUptimeNanos == 1L && unixNanos == 1L
44     @JsName("isMax")
45     val isMax =
46         elapsedNanos == Long.MAX_VALUE &&
47             systemUptimeNanos == Long.MAX_VALUE &&
48             unixNanos == Long.MAX_VALUE
49 
unixNanosToLogFormatnull50     fun unixNanosToLogFormat(): String {
51         val seconds = unixNanos / SECOND_AS_NANOSECONDS
52         val nanos = unixNanos % SECOND_AS_NANOSECONDS
53         return "$seconds.${nanos.toString().padStart(9, '0')}"
54     }
55 
toStringnull56     override fun toString(): String {
57         if (isEmpty) {
58             return "<NO TIMESTAMP>"
59         }
60 
61         if (isMin) {
62             return "TIMESTAMP.MIN"
63         }
64 
65         if (isMax) {
66             return "TIMESTAMP.MAX"
67         }
68 
69         return buildString {
70             append("Timestamp(")
71             append(
72                 mutableListOf<String>()
73                     .apply {
74                         if (hasUnixTimestamp) {
75                             add("UNIX=${realTimestampFormatter(unixNanos)}(${unixNanos}ns)")
76                         } else {
77                             add("UNIX=${unixNanos}ns")
78                         }
79                         if (hasSystemUptimeTimestamp) {
80                             add(
81                                 "UPTIME=${formatElapsedTimestamp(systemUptimeNanos)}" +
82                                     "(${systemUptimeNanos}ns)"
83                             )
84                         } else {
85                             add("UPTIME=${systemUptimeNanos}ns")
86                         }
87                         if (hasElapsedTimestamp) {
88                             add(
89                                 "ELAPSED=${formatElapsedTimestamp(elapsedNanos)}(${elapsedNanos}ns)"
90                             )
91                         } else {
92                             add("ELAPSED=${elapsedNanos}ns")
93                         }
94                     }
95                     .joinToString()
96             )
97             append(")")
98         }
99     }
100 
101     @JsName("minusLong")
minusnull102     operator fun minus(nanos: Long): Timestamp {
103         val elapsedNanos = max(this.elapsedNanos - nanos, 0L)
104         val systemUptimeNanos = max(this.systemUptimeNanos - nanos, 0L)
105         val unixNanos = max(this.unixNanos - nanos, 0L)
106         return Timestamp(elapsedNanos, systemUptimeNanos, unixNanos, realTimestampFormatter)
107     }
108 
109     @JsName("minusTimestamp")
minusnull110     operator fun minus(timestamp: Timestamp): Timestamp {
111         val elapsedNanos =
112             if (this.hasElapsedTimestamp && timestamp.hasElapsedTimestamp) {
113                 this.elapsedNanos - timestamp.elapsedNanos
114             } else {
115                 0L
116             }
117         val systemUptimeNanos =
118             if (this.hasSystemUptimeTimestamp && timestamp.hasSystemUptimeTimestamp) {
119                 this.systemUptimeNanos - timestamp.systemUptimeNanos
120             } else {
121                 0L
122             }
123         val unixNanos =
124             if (this.hasUnixTimestamp && timestamp.hasUnixTimestamp) {
125                 this.unixNanos - timestamp.unixNanos
126             } else {
127                 0L
128             }
129         return Timestamp(elapsedNanos, systemUptimeNanos, unixNanos, realTimestampFormatter)
130     }
131 
132     enum class PreferredType {
133         ELAPSED,
134         SYSTEM_UPTIME,
135         UNIX,
136         ANY
137     }
138 
139     // The preferred and most accurate time type to use when running Timestamp operations or
140     // comparisons
141     private val preferredType: PreferredType
142         get() =
143             when {
144                 hasElapsedTimestamp && hasSystemUptimeTimestamp -> PreferredType.ANY
145                 hasElapsedTimestamp -> PreferredType.ELAPSED
146                 hasSystemUptimeTimestamp -> PreferredType.SYSTEM_UPTIME
147                 hasUnixTimestamp -> PreferredType.UNIX
148                 else -> error("No valid timestamp available")
149             }
150 
compareTonull151     override fun compareTo(other: Timestamp): Int {
152         var useType = PreferredType.ANY
153         if (other.preferredType == this.preferredType) {
154             useType = this.preferredType
155         } else if (this.preferredType == PreferredType.ANY) {
156             useType = other.preferredType
157         } else if (other.preferredType == PreferredType.ANY) {
158             useType = this.preferredType
159         }
160 
161         return when (useType) {
162             PreferredType.ELAPSED -> this.elapsedNanos.compareTo(other.elapsedNanos)
163             PreferredType.SYSTEM_UPTIME -> this.systemUptimeNanos.compareTo(other.systemUptimeNanos)
164             PreferredType.UNIX,
165             PreferredType.ANY -> {
166                 when {
167                     // If preferred timestamps don't match then comparing UNIX timestamps is
168                     // probably most accurate
169                     this.hasUnixTimestamp && other.hasUnixTimestamp ->
170                         this.unixNanos.compareTo(other.unixNanos)
171                     // Assumes timestamps are collected from the same device
172                     this.hasElapsedTimestamp && other.hasElapsedTimestamp ->
173                         this.elapsedNanos.compareTo(other.elapsedNanos)
174                     this.hasSystemUptimeTimestamp && other.hasSystemUptimeTimestamp ->
175                         this.systemUptimeNanos.compareTo(other.systemUptimeNanos)
176                     else -> error("Timestamps $this and $other are not comparable")
177                 }
178             }
179         }
180     }
181 
182     companion object {
formatElapsedTimestampnull183         fun formatElapsedTimestamp(timestampNs: Long): String {
184             var remainingNs = timestampNs
185             val prettyTimestamp = StringBuilder()
186 
187             val timeUnitToNanoSeconds =
188                 mapOf(
189                     "d" to DAY_AS_NANOSECONDS,
190                     "h" to HOUR_AS_NANOSECONDS,
191                     "m" to MINUTE_AS_NANOSECONDS,
192                     "s" to SECOND_AS_NANOSECONDS,
193                     "ms" to MILLISECOND_AS_NANOSECONDS,
194                     "ns" to 1,
195                 )
196 
197             for ((timeUnit, ns) in timeUnitToNanoSeconds) {
198                 val convertedTime = remainingNs / ns
199                 remainingNs %= ns
200                 if (prettyTimestamp.isEmpty() && convertedTime == 0L) {
201                     // Trailing 0 unit
202                     continue
203                 }
204                 prettyTimestamp.append("$convertedTime$timeUnit")
205             }
206 
207             if (prettyTimestamp.isEmpty()) {
208                 return "0ns"
209             }
210 
211             return prettyTimestamp.toString()
212         }
213     }
214 }
215