From bb2e7f385214f0bdcdb04ec324558a91bfbd80e8 Mon Sep 17 00:00:00 2001 From: Sarah Haggarty Date: Thu, 1 Jun 2023 10:29:58 +0200 Subject: [PATCH] feat: add time measurement guide --- docs/kr.tree | 3 +- docs/topics/time-measurement.md | 415 ++++++++++++++++++++++++++++++++ 2 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 docs/topics/time-measurement.md diff --git a/docs/kr.tree b/docs/kr.tree index 910a808f5a6..1a77f7b947d 100644 --- a/docs/kr.tree +++ b/docs/kr.tree @@ -306,8 +306,9 @@ - + + diff --git a/docs/topics/time-measurement.md b/docs/topics/time-measurement.md new file mode 100644 index 00000000000..1f4a5fe2d6c --- /dev/null +++ b/docs/topics/time-measurement.md @@ -0,0 +1,415 @@ +[//]: # (title: Time measurement) + +The Kotlin standard library gives you the tools to calculate and measure time in different units. +Accurate time measurement is important for activities like: + * Managing threads or processes + * Collecting statistics + * Detecting timeouts + * Debugging + +By default, time is measured using a monotonic time source, but other time sources can be configured. +For more information, see [Create time source](#create-time-source). + +## Calculate duration + +To represent an amount of time, the standard library has the [`Duration`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/) +class. A `Duration` can be expressed in the following units from the [`DurationUnit`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration-unit/) +enum class: + * `NANOSECONDS` + * `MICROSECONDS` + * `MILLISECONDS` + * `SECONDS` + * `MINUTES` + * `HOURS` + * `DAYS` + +A `Duration` can be positive, negative, zero, positive infinity, or negative infinity. + +### Create duration + +To create a `Duration`, use the [extension properties](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/#companion-object-properties) +available for `Int`, `Long`, and `Double` types: `nanoseconds`, `microseconds`, `milliseconds`, `seconds`, `minutes`, +`hours`, and `days`. + +> Days refer to periods of 24 hours. They are not calendar days. +> +{type = "tip"} + +For example: + +```kotlin +import kotlin.time.* +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.days + +fun main() { +//sampleStart + val fiveHundredMilliseconds: Duration = 500.milliseconds + val zeroSeconds: Duration = 0.seconds + val tenMinutes: Duration = 10.minutes + val negativeNanosecond: Duration = (-1).nanoseconds + val infiniteDays: Duration = Double.POSITIVE_INFINITY.days + val negativeInfiniteDays: Duration = Double.NEGATIVE_INFINITY.days + + println(fiveHundredMilliseconds) // 500ms + println(zeroSeconds) // 0s + println(tenMinutes) // 10m + println(negativeNanosecond) // -1ns + println(infiniteDays) // Infinity + println(negativeInfiniteDays) // -Infinity +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-create-duration"} + +You can also perform basic arithmetic with `Duration` objects: + +```kotlin +import kotlin.time.* +import kotlin.time.Duration.Companion.seconds + +fun main() { +//sampleStart + val fiveSeconds: Duration = 5.seconds + val thirtySeconds: Duration = 30.seconds + + println(fiveSeconds + thirtySeconds) + // 35s + println(thirtySeconds - fiveSeconds) + // 25s + println(fiveSeconds * 2) + // 10s + println(thirtySeconds / 2) + // 15s + println(thirtySeconds / fiveSeconds) + // 6.0 + println(-thirtySeconds) + // -30s + println((-thirtySeconds).absoluteValue) + // 30s +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-create-duration-arithmetic"} + +### Get string representation + +It can be useful to have a string representation of a `Duration` so that you can print, serialize, transfer, or store it. + +To get a string representation, use the `.toString()` function. By default, the time is reported using each unit that is +present. For example: `1h 0m 45.677s` or `-(6d 5h 5m 28.284s)` + +To configure the output, use the `.toString()` function with your desired `DurationUnit` and number of decimal places +as function parameters: + +```kotlin +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.DurationUnit + +fun main() { +//sampleStart + // Print in seconds with 2 decimal places + println(5887.milliseconds.toString(DurationUnit.SECONDS, 2)) + // 5.89s +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-string-representation"} + +To get an [ISO-8601-compatible](https://en.wikipedia.org/wiki/ISO_8601) string, use the [`toIsoString()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/to-iso-string.html) +function: + +```kotlin +import kotlin.time.Duration.Companion.seconds + +fun main() { +//sampleStart + println(86420.seconds.toIsoString()) // PT24H0M20S +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-iso-string-representation"} + +### Convert duration + +To convert your `Duration` into a different `DurationUnit`, use the following properties: +* `inWholeNanoseconds` +* `inWholeMicroseconds` +* `inWholeSeconds` +* `inWholeMinutes` +* `inWholeHours` +* `inWholeDays` + +For example: + +```kotlin +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes + +fun main() { +//sampleStart + val thirtyMinutes: Duration = 30.minutes + println(thirtyMinutes.inWholeSeconds) + // 1800 +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-convert-duration"} + +Alternatively, you can use your desired `DurationUnit` as a function parameter in the following extension functions: +* `.toInt()` +* `.toDouble()` +* `.toLong()` + +For example: + +```kotlin +import kotlin.time.Duration.Companion.seconds +import kotlin.time.DurationUnit + +fun main() { +//sampleStart + println(270.seconds.toDouble(DurationUnit.MINUTES)) + // 4.5 +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-convert-duration-extension"} + +### Compare duration + +To check if `Duration` objects are equal, use the equality operator (`==`): + +```kotlin +import kotlin.time.Duration +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.minutes + +fun main() { +//sampleStart + val thirtyMinutes: Duration = 30.minutes + val halfHour: Duration = 0.5.hours + println(thirtyMinutes == halfHour) + // true +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-equality-duration"} + +To compare `Duration` objects, use the comparison operators (`<`, `>`): + +```kotlin +import kotlin.time.Duration.Companion.microseconds +import kotlin.time.Duration.Companion.nanoseconds + +fun main() { +//sampleStart + println(3000.microseconds < 25000.nanoseconds) + // false +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-compare-duration"} + +### Break duration into components + +To break down a `Duration` into its time components and perform a further action, use the overload of the +[`toComponents()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/to-components.html) function. +Add your desired action as a function or lambda expression as a function parameter. + +For example: + +```kotlin +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes + +fun main() { +//sampleStart + val thirtyMinutes: Duration = 30.minutes + println(thirtyMinutes.toComponents { hours, minutes, _, _ -> "${hours}h:${minutes}m" }) + // 0h:30m +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-duration-components"} + +In this example, the lambda expression has `hours` and `minutes` as function parameters with underscores (`_`) for the +unused `seconds` and `nanoseconds` parameters. The expression returns a concatenated string using [string templates](strings.md#string-templates) +to get the desired output format of `hours` and `minutes`. + +## Measure time + +To track the passage of time, the standard library provides tools so that you can easily: +* Measure the time taken to execute some code with your desired time unit. +* Mark a moment in time. +* Compare and subtract two moments in time. +* Check how much time has passed since a specific moment in time. +* Check whether the current time has passed a specific moment in time. + +### Measure code execution time + +To measure the time taken to execute a block of code, use the [`measureTime`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/measure-time.html) +inline function: + +```kotlin +import kotlin.time.measureTime + +fun main() { +//sampleStart + val timeTaken = measureTime { + Thread.sleep(100) + } + println(timeTaken) // e.g. 103 ms +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-measure-time"} + +To measure the time taken to execute a block of code **and** return the value of the block of code, use inline function [`measureTimedValue`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/measure-time.html). + +For example: + +```kotlin +import kotlin.time.measureTimedValue + +fun main() { +//sampleStart + val (value, timeTaken) = measureTimedValue { + Thread.sleep(100) + 42 + } + println(value) // 42 + println(timeTaken) // e.g. 103 ms +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-measure-timed-value"} + +By default, both functions use a monotonic time source. + +### Mark moments in time + +To mark a specific moment in time, use the [`TimeSource`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-time-source/) +interface and the [`markNow()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-time-source/mark-now.html) function +to create a [`TimeMark`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-time-mark/): + +```kotlin +import kotlin.time.* + +fun main() { +//sampleStart + val timeSource = TimeSource.Monotonic + val mark = timeSource.markNow() +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-mark-moment"} + +### Measure differences in time + +To measure differences between `TimeMark` objects from the same time source, use the subtraction operator (`-`). + +To compare `TimeMark` objects from the same time source, use the comparison operators (`<`, `>`). + +For example: + +```kotlin +import kotlin.time.* + +fun main() { +//sampleStart + val timeSource = TimeSource.Monotonic + val mark1 = timeSource.markNow() + Thread.sleep(500) // Sleep 0.5 seconds. + val mark2 = timeSource.markNow() + + repeat(4) { n -> + val mark3 = timeSource.markNow() + val elapsed1 = mark3 - mark1 + val elapsed2 = mark3 - mark2 + + println("Measurement 1.${n + 1}: elapsed1=$elapsed1, elapsed2=$elapsed2, diff=${elapsed1 - elapsed2}") + } + + println(mark2 > mark1) // This is true, as mark2 was captured later than mark1. + // true +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-measure-difference"} + +To check if a deadline has passed or a timeout has been reached, use the [`hasPassedNow()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-time-mark/has-passed-now.html) +and [`hasNotPassedNow()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-time-mark/has-not-passed-now.html) +extension functions: + +```kotlin +import kotlin.time.* +import kotlin.time.Duration.Companion.seconds + +fun main() { +//sampleStart + val timeSource = TimeSource.Monotonic + val mark1 = timeSource.markNow() + val fiveSeconds: Duration = 5.seconds + val mark2 = mark1 + fiveSeconds + + // It hasn't been 5 seconds yet + println(mark2.hasPassedNow()) + // false + + // Wait six seconds + Thread.sleep(6000) + println(mark2.hasPassedNow()) + // true + +//sampleEnd +} +``` +{kotlin-runnable="true" kotlin-min-compiler-version="1.3" id="kotlin-time-deadline=passed"} + +## Time sources + +By default, time is measured using a monotonic time source. Monotonic time sources only move forward and are not affected +by variations like timezones. An alternative to monotonic time is elapsed real time which is also known as wall-clock time. +Elapsed real time is measured relative to another point in time. + +### Default time sources per platform + +This table explains the default source of monotonic time for each platform: + +| Platform | Source | +|---------------------|---| +| Kotlin/JVM | `System.nanoTime()`| +| Kotlin/JS (Node.js) | `process.hrtime()`| +| Kotlin/JS (browser) | `window.performance.now()` or `Date.now()`| +| Kotlin/Native | `std::chrono::high_resolution_clock` or `std::chrono::steady_clock`| + +### Create time source + +There are some cases where you might want to use a different time source. For example in Android, `System.nanoTime()` +only counts time while the device is active. It loses track of time when the device enters deep sleep. To keep track of +time while the device is in deep sleep, you can create a time source that uses [`SystemClock.elapsedRealtimeNanos()`](https://developer.android.com/reference/android/os/SystemClock#elapsedRealtimeNanos()): + +```kotlin +object RealtimeMonotonicTimeSource : AbstractLongTimeSource(DurationUnit.NANOSECONDS) { + override fun read(): Long = SystemClock.elapsedRealtimeNanos() +} +``` + +Then you can use your time source to make time measurements: + +```kotlin +fun main() { + val elapsed: Duration = RealtimeMonotonicTimeSource.measureTime { + Thread.sleep(100) + } + println(elapsed) // e.g. 103 ms +} +``` +{validate="false"} + +For more information about the `kotlin.time` package, see our [standard library API reference](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/). \ No newline at end of file