From ca29cfb88ddba7f6d00e8ac784a32eb65b9bbcef Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 11 Apr 2019 19:59:47 +0300 Subject: [PATCH] Implement MonoClock in JS It uses either process.hrtime() in node.js or performance.now() in browser, falling back to Date.now() if the latter is not available --- .../stdlib/js/src/kotlin/time/MonoClock.kt | 49 +++++++++++++++++-- libraries/stdlib/test/time/MeasureTimeTest.kt | 47 ++++++++++++++++++ 2 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 libraries/stdlib/test/time/MeasureTimeTest.kt diff --git a/libraries/stdlib/js/src/kotlin/time/MonoClock.kt b/libraries/stdlib/js/src/kotlin/time/MonoClock.kt index d558dcefe78..81f9b86a6b6 100644 --- a/libraries/stdlib/js/src/kotlin/time/MonoClock.kt +++ b/libraries/stdlib/js/src/kotlin/time/MonoClock.kt @@ -1,12 +1,53 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license - * that can be found in the license/LICENSE.txt file. + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package kotlin.time +import org.w3c.performance.GlobalPerformance +import org.w3c.performance.Performance + public actual object MonoClock : Clock { - override fun mark(initialElapsed: Duration): ClockMark { - TODO("not implemented: base on high-res browser or node perf counter") + + private val actualClock: Clock = run { + val isNode: Boolean = js("typeof process !== 'undefined' && process.versions && !!process.versions.node") + + if (isNode) + HrTimeClock(js("process").unsafeCast()) + else + js("self").unsafeCast()?.performance?.let(::PerformanceClock) + ?: DateNowClock + } + + override fun mark(initialElapsed: Duration): ClockMark = actualClock.mark(initialElapsed) +} + +internal external interface Process { + fun hrtime(time: Array = definedExternally): Array +} + +internal class HrTimeClock(val process: Process) : Clock { + + override fun mark(initialElapsed: Duration): ClockMark = object : ClockMark { + val startedAt = process.hrtime() + override val clock: Clock get() = this@HrTimeClock // delegation problem? + override val elapsedFrom: Duration + get() = process.hrtime(startedAt).let { (seconds, nanos) -> seconds.seconds + nanos.nanoseconds + initialElapsed } + } + + override fun toString(): String = "Clock(process.hrtime())" +} + +internal class PerformanceClock(val performance: Performance) : DoubleReadingClock() { + override val unit: DurationUnit get() = DurationUnit.MILLISECONDS + override fun reading(): Double = performance.now() + override fun toString(): String = "Clock(self.performance.now())" +} + +internal object DateNowClock : DoubleReadingClock() { + override val unit: DurationUnit get() = DurationUnit.MILLISECONDS + override fun reading(): Double = kotlin.js.Date.now() + override fun toString(): String = "Clock(Date.now())" } \ No newline at end of file diff --git a/libraries/stdlib/test/time/MeasureTimeTest.kt b/libraries/stdlib/test/time/MeasureTimeTest.kt new file mode 100644 index 00000000000..fa68d1e2021 --- /dev/null +++ b/libraries/stdlib/test/time/MeasureTimeTest.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package test.time + +import kotlin.random.Random +import kotlin.test.* +import kotlin.time.* + +class MeasureTimeTest { + + private fun longRunningCalc(): String = buildString { + repeat(10) { + while (Random.nextDouble() >= 0.001); + append(('a'..'z').random()) + } + } + + @Test + fun measureTimeOfCalc() { + val someResult: String + + val elapsed = measureTime { + someResult = longRunningCalc() + } + + println("elapsed: $elapsed") + + assertEquals(10, someResult.length) + assertTrue(elapsed > Duration.ZERO) + } + + @Test + fun measureTimeAndResult() { + val someResult: String + + val measured: DurationMeasured = withMeasureTime { longRunningCalc().also { someResult = it } } + println("measured: $measured") + + val (result, elapsed) = measured + + assertEquals(someResult, result) + assertTrue(elapsed > Duration.ZERO) + } +} \ No newline at end of file