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