From 49467eea65adaef746972279ed4d9012cf326cb9 Mon Sep 17 00:00:00 2001 From: Mikolaj Grzaslewicz Date: Fri, 12 Jan 2024 13:55:02 +0100 Subject: [PATCH] JPERF-1454 Add timeOrigin --- CHANGELOG.md | 5 +++++ .../api/w3c/JavascriptW3cPerformanceTimeline.kt | 9 ++++++++- .../api/w3c/RecordedPerformanceEntries.kt | 6 +++++- .../tools/jiraactions/w3c/VerboseJsonFormat.kt | 10 ++++++++-- .../tools/jiraactions/w3c/harvesters/Common.kt | 16 ++++++++++++++++ .../jiraactions/api/measure/ActionMeterTest.kt | 6 +++--- .../output/AppendableActionMetricOutputTest.kt | 3 ++- .../api/scenario/AbstractJiraCoreScenario.kt | 7 +++++++ 8 files changed, 54 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c21910f..f8d13ed4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,11 @@ Dropping a requirement of a major version of a dependency is a new contract. ## [Unreleased] [Unreleased]: https://github.com/atlassian/jira-actions/compare/release-3.26.0...master +### Added +- Add `RecordedPerformanceEntries.timeOrigin`. Help with [JPERF-1454]. + +[JPERF-1454]: https://ecosystem.atlassian.net/browse/JPERF-1454 + ## [3.26.0] - 2023-11-30 [3.26.0]: https://github.com/atlassian/jira-actions/compare/release-3.25.0...release-3.26.0 diff --git a/src/main/kotlin/com/atlassian/performance/tools/jiraactions/api/w3c/JavascriptW3cPerformanceTimeline.kt b/src/main/kotlin/com/atlassian/performance/tools/jiraactions/api/w3c/JavascriptW3cPerformanceTimeline.kt index 70e7691c..97e5dcdf 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/jiraactions/api/w3c/JavascriptW3cPerformanceTimeline.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/jiraactions/api/w3c/JavascriptW3cPerformanceTimeline.kt @@ -3,7 +3,9 @@ package com.atlassian.performance.tools.jiraactions.api.w3c import com.atlassian.performance.tools.jiraactions.w3c.harvesters.getJsElementsPerformance import com.atlassian.performance.tools.jiraactions.w3c.harvesters.getJsNavigationsPerformance import com.atlassian.performance.tools.jiraactions.w3c.harvesters.getJsResourcesPerformance +import com.atlassian.performance.tools.jiraactions.w3c.harvesters.parseInstantMilli import org.openqa.selenium.JavascriptExecutor +import java.time.Instant /** * Obtains entries from [javascript]. @@ -24,10 +26,15 @@ class JavascriptW3cPerformanceTimeline private constructor( return RecordedPerformanceEntries( navigations = if (recordNavigation) getJsNavigationsPerformance(javascript) else emptyList(), resources = if (recordResources) getJsResourcesPerformance(javascript) else emptyList(), - elements = if (recordElements) getJsElementsPerformance(javascript) else emptyList() + elements = if (recordElements) getJsElementsPerformance(javascript) else emptyList(), + timeOrigin = getTimeOrigin(javascript) ) } + private fun getTimeOrigin(javascript: JavascriptExecutor): Instant { + return parseInstantMilli(javascript.executeScript("return window.performance.timeOrigin;")) + } + class Builder( private var javascript: JavascriptExecutor ) { diff --git a/src/main/kotlin/com/atlassian/performance/tools/jiraactions/api/w3c/RecordedPerformanceEntries.kt b/src/main/kotlin/com/atlassian/performance/tools/jiraactions/api/w3c/RecordedPerformanceEntries.kt index 63f7923b..8215cdd8 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/jiraactions/api/w3c/RecordedPerformanceEntries.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/jiraactions/api/w3c/RecordedPerformanceEntries.kt @@ -1,5 +1,7 @@ package com.atlassian.performance.tools.jiraactions.api.w3c +import java.time.Instant + /** * Holds interesting recorded performance entries. * They share the same timeline and can be cross-examined. @@ -7,8 +9,10 @@ package com.atlassian.performance.tools.jiraactions.api.w3c class RecordedPerformanceEntries internal constructor( val navigations: List, val resources: List, - val elements: List + val elements: List, + val timeOrigin: Instant? ) { + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/src/main/kotlin/com/atlassian/performance/tools/jiraactions/w3c/VerboseJsonFormat.kt b/src/main/kotlin/com/atlassian/performance/tools/jiraactions/w3c/VerboseJsonFormat.kt index 5dbadc51..50d06d63 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/jiraactions/w3c/VerboseJsonFormat.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/jiraactions/w3c/VerboseJsonFormat.kt @@ -3,7 +3,7 @@ package com.atlassian.performance.tools.jiraactions.w3c import com.atlassian.performance.tools.jiraactions.JsonProviderSingleton.JSON import com.atlassian.performance.tools.jiraactions.api.w3c.* import java.time.Duration -import javax.json.Json +import java.time.Instant import javax.json.JsonArray import javax.json.JsonObject @@ -16,6 +16,11 @@ internal class VerboseJsonFormat { .add("navigations", navigations.map { serializeNavigationTiming(it) }.toJsonArray()) .add("resources", resources.map { serializeResourceTiming(it) }.toJsonArray()) .add("elements", (elements.map { serializeElementTiming(it) }).toJsonArray()) + .apply { + if (timeOrigin != null) { + add("timeOrigin", timeOrigin.toString()) + } + } .build() } @@ -32,7 +37,8 @@ internal class VerboseJsonFormat { elements = getJsonArray("elements") ?.map { it.asJsonObject() } ?.map { deserializeElementTiming(it) } - ?: emptyList() + ?: emptyList(), + timeOrigin = getJsonString("timeOrigin")?.let { Instant.parse(it.string) } ) } diff --git a/src/main/kotlin/com/atlassian/performance/tools/jiraactions/w3c/harvesters/Common.kt b/src/main/kotlin/com/atlassian/performance/tools/jiraactions/w3c/harvesters/Common.kt index 5bbd5956..821c7ce8 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/jiraactions/w3c/harvesters/Common.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/jiraactions/w3c/harvesters/Common.kt @@ -1,6 +1,8 @@ package com.atlassian.performance.tools.jiraactions.w3c.harvesters import java.time.Duration +import java.time.Instant +import kotlin.math.floor internal fun parseTimestamp( timestamp: Any? @@ -9,3 +11,17 @@ internal fun parseTimestamp( is Double -> Duration.ofMillis(timestamp.toLong()) else -> throw Exception("Cannot parse timestamp from $timestamp") } + +internal fun parseInstantMilli( + milli: Any? +): Instant = when (milli) { + is Long -> Instant.ofEpochMilli(milli) + is Double -> { + val wholeMilli: Long = floor(milli).toLong() + val remainderMilli: Double = milli - wholeMilli + val remainderNanos: Long = (remainderMilli * 1_000_000).toLong() + Instant.ofEpochMilli(wholeMilli).plusNanos(remainderNanos) + } + + else -> throw Exception("Cannot parse instant from $milli") +} diff --git a/src/test/kotlin/com/atlassian/performance/tools/jiraactions/api/measure/ActionMeterTest.kt b/src/test/kotlin/com/atlassian/performance/tools/jiraactions/api/measure/ActionMeterTest.kt index e43aab78..c8c42301 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/jiraactions/api/measure/ActionMeterTest.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/jiraactions/api/measure/ActionMeterTest.kt @@ -67,7 +67,7 @@ class ActionMeterTest { result: ActionResult, duration: Duration, start: Instant, - entries: RecordedPerformanceEntries? = RecordedPerformanceEntries(emptyList(), emptyList(), emptyList()) + entries: RecordedPerformanceEntries? = RecordedPerformanceEntries(emptyList(), emptyList(), emptyList(), null) ): ActionMetric = ActionMetric.Builder( label = actionType.label, result = result, @@ -83,7 +83,7 @@ class ActionMeterTest { @Test fun shouldMeasureErrors() { val output = CollectionActionMetricOutput(mutableListOf()) - val entries = RecordedPerformanceEntries(emptyList(), emptyList(), emptyList()) + val entries = RecordedPerformanceEntries(emptyList(), emptyList(), emptyList(), null) val actionMeter = ActionMeter.Builder( output = output ) @@ -112,7 +112,7 @@ class ActionMeterTest { val clock = TickingClock(start, tick) val output = CollectionActionMetricOutput(mutableListOf()) val w3cPerformanceTimelineMock = - HardcodedTimeline(RecordedPerformanceEntries(emptyList(), emptyList(), emptyList())) + HardcodedTimeline(RecordedPerformanceEntries(emptyList(), emptyList(), emptyList(), null)) val actionMeter = ActionMeter.Builder( output = output ) diff --git a/src/test/kotlin/com/atlassian/performance/tools/jiraactions/api/measure/output/AppendableActionMetricOutputTest.kt b/src/test/kotlin/com/atlassian/performance/tools/jiraactions/api/measure/output/AppendableActionMetricOutputTest.kt index 75b07b89..c2ab38eb 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/jiraactions/api/measure/output/AppendableActionMetricOutputTest.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/jiraactions/api/measure/output/AppendableActionMetricOutputTest.kt @@ -179,6 +179,7 @@ class AppendableActionMetricOutputTest { id = "home_link", url = "" ) - ) + ), + timeOrigin = null ) } diff --git a/src/test/kotlin/com/atlassian/performance/tools/jiraactions/api/scenario/AbstractJiraCoreScenario.kt b/src/test/kotlin/com/atlassian/performance/tools/jiraactions/api/scenario/AbstractJiraCoreScenario.kt index f76bc9df..1728857c 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/jiraactions/api/scenario/AbstractJiraCoreScenario.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/jiraactions/api/scenario/AbstractJiraCoreScenario.kt @@ -18,6 +18,9 @@ import org.openqa.selenium.By import org.openqa.selenium.JavascriptExecutor import org.openqa.selenium.OutputType import org.openqa.selenium.remote.RemoteWebDriver +import java.time.LocalDate +import java.time.ZoneId +import java.time.ZonedDateTime abstract class AbstractJiraCoreScenario { private val logger: Logger = LogManager.getLogger(this::class.java) @@ -98,6 +101,10 @@ abstract class AbstractJiraCoreScenario { private fun assertDrilldown(metrics: List) { val navigationsPerMetric = metrics.map { it.drilldown?.navigations ?: emptyList() } assertThat(navigationsPerMetric).`as`("all results contain timings in drilldown").hasSize(metrics.size) + val timeOrigin = metrics[0].drilldown?.timeOrigin + assertThat(timeOrigin).isNotNull() + val dateTimeOrigin = ZonedDateTime.ofInstant(timeOrigin, ZoneId.of("UTC")) + assertThat(dateTimeOrigin.year).isBetween(2024, 2060) } private fun goToServices(driver: RemoteWebDriver, jira: Jira) {