| /* |
| * Copyright (C) 2023 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.tools.common |
| |
| import kotlin.js.JsExport |
| import kotlin.js.JsName |
| import kotlin.math.max |
| |
| /** |
| * Time interface with all available timestamp types |
| * |
| * @param elapsedNanos Nanoseconds since boot, including time spent in sleep. |
| * @param systemUptimeNanos Nanoseconds since boot, not counting time spent in deep sleep |
| * @param unixNanos Nanoseconds since Unix epoch |
| */ |
| @JsExport |
| data class Timestamp |
| internal constructor( |
| val elapsedNanos: Long = 0L, |
| val systemUptimeNanos: Long = 0L, |
| val unixNanos: Long = 0L, |
| private val realTimestampFormatter: (Long) -> String |
| ) : Comparable<Timestamp> { |
| val hasElapsedTimestamp = elapsedNanos != 0L |
| val hasSystemUptimeTimestamp = systemUptimeNanos != 0L |
| val hasUnixTimestamp = unixNanos != 0L |
| val isEmpty = !hasElapsedTimestamp && !hasSystemUptimeTimestamp && !hasUnixTimestamp |
| val hasAllTimestamps = hasUnixTimestamp && hasSystemUptimeTimestamp && hasElapsedTimestamp |
| |
| fun unixNanosToLogFormat(): String { |
| val seconds = unixNanos / SECOND_AS_NANOSECONDS |
| val nanos = unixNanos % SECOND_AS_NANOSECONDS |
| return "$seconds.${nanos.toString().padStart(9, '0')}" |
| } |
| |
| override fun toString(): String { |
| return when { |
| isEmpty -> "<NO TIMESTAMP>" |
| hasUnixTimestamp -> "${realTimestampFormatter(unixNanos)} (${unixNanos}ns)" |
| hasSystemUptimeTimestamp -> |
| "${formatElapsedTimestamp(systemUptimeNanos)} (${systemUptimeNanos}ns)" |
| hasElapsedTimestamp -> "${formatElapsedTimestamp(elapsedNanos)} (${elapsedNanos}ns)" |
| else -> error("Timestamp had no valid timestamps sets") |
| } |
| } |
| |
| @JsName("minusLong") |
| operator fun minus(nanos: Long): Timestamp { |
| val elapsedNanos = max(this.elapsedNanos - nanos, 0L) |
| val systemUptimeNanos = max(this.systemUptimeNanos - nanos, 0L) |
| val unixNanos = max(this.unixNanos - nanos, 0L) |
| return Timestamp(elapsedNanos, systemUptimeNanos, unixNanos, realTimestampFormatter) |
| } |
| |
| @JsName("minusTimestamp") |
| operator fun minus(timestamp: Timestamp): Timestamp { |
| val elapsedNanos = |
| if (this.hasElapsedTimestamp && timestamp.hasElapsedTimestamp) { |
| this.elapsedNanos - timestamp.elapsedNanos |
| } else { |
| 0L |
| } |
| val systemUptimeNanos = |
| if (this.hasSystemUptimeTimestamp && timestamp.hasSystemUptimeTimestamp) { |
| this.systemUptimeNanos - timestamp.systemUptimeNanos |
| } else { |
| 0L |
| } |
| val unixNanos = |
| if (this.hasUnixTimestamp && timestamp.hasUnixTimestamp) { |
| this.unixNanos - timestamp.unixNanos |
| } else { |
| 0L |
| } |
| return Timestamp(elapsedNanos, systemUptimeNanos, unixNanos, realTimestampFormatter) |
| } |
| |
| enum class PreferredType { |
| ELAPSED, |
| SYSTEM_UPTIME, |
| UNIX, |
| ANY |
| } |
| |
| // The preferred and most accurate time type to use when running Timestamp operations or |
| // comparisons |
| private val preferredType: PreferredType |
| get() = |
| when { |
| hasElapsedTimestamp && hasSystemUptimeTimestamp -> PreferredType.ANY |
| hasElapsedTimestamp -> PreferredType.ELAPSED |
| hasSystemUptimeTimestamp -> PreferredType.SYSTEM_UPTIME |
| hasUnixTimestamp -> PreferredType.UNIX |
| else -> error("No valid timestamp available") |
| } |
| |
| override fun compareTo(other: Timestamp): Int { |
| var useType = PreferredType.ANY |
| if (other.preferredType == this.preferredType) { |
| useType = this.preferredType |
| } else if (this.preferredType == PreferredType.ANY) { |
| useType = other.preferredType |
| } else if (other.preferredType == PreferredType.ANY) { |
| useType = this.preferredType |
| } |
| |
| return when (useType) { |
| PreferredType.ELAPSED -> this.elapsedNanos.compareTo(other.elapsedNanos) |
| PreferredType.SYSTEM_UPTIME -> this.systemUptimeNanos.compareTo(other.systemUptimeNanos) |
| PreferredType.UNIX, |
| PreferredType.ANY -> { |
| when { |
| // If preferred timestamps don't match then comparing UNIX timestamps is |
| // probably most accurate |
| this.hasUnixTimestamp && other.hasUnixTimestamp -> |
| this.unixNanos.compareTo(other.unixNanos) |
| // Assumes timestamps are collected from the same device |
| this.hasElapsedTimestamp && other.hasElapsedTimestamp -> |
| this.elapsedNanos.compareTo(other.elapsedNanos) |
| this.hasSystemUptimeTimestamp && other.hasSystemUptimeTimestamp -> |
| this.systemUptimeNanos.compareTo(other.systemUptimeNanos) |
| else -> error("Timestamps $this and $other are not comparable") |
| } |
| } |
| } |
| } |
| |
| companion object { |
| fun formatElapsedTimestamp(timestampNs: Long): String { |
| var remainingNs = timestampNs |
| val prettyTimestamp = StringBuilder() |
| |
| val timeUnitToNanoSeconds = |
| mapOf( |
| "d" to DAY_AS_NANOSECONDS, |
| "h" to HOUR_AS_NANOSECONDS, |
| "m" to MINUTE_AS_NANOSECONDS, |
| "s" to SECOND_AS_NANOSECONDS, |
| "ms" to MILLISECOND_AS_NANOSECONDS, |
| "ns" to 1, |
| ) |
| |
| for ((timeUnit, ns) in timeUnitToNanoSeconds) { |
| val convertedTime = remainingNs / ns |
| remainingNs %= ns |
| if (prettyTimestamp.isEmpty() && convertedTime == 0L) { |
| // Trailing 0 unit |
| continue |
| } |
| prettyTimestamp.append("$convertedTime$timeUnit") |
| } |
| |
| if (prettyTimestamp.isEmpty()) { |
| return "0ns" |
| } |
| |
| return prettyTimestamp.toString() |
| } |
| } |
| } |