Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JPERF-1435: remove JS header parsing #164

Merged
merged 3 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,18 @@ Adding a requirement of a major version of a dependency is breaking a contract.
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.24.0...master
[Unreleased]: https://github.com/atlassian/jira-actions/compare/release-3.25.0...master

### Deprecated
- Deprecate `ActionMetric.toBackendTimeSlots`. It belongs to a module, which can:
- formalize the dependency on `Server-Timing`s convention (e.g. `response-thread-plugin` public behavioral API)
- encapsulate a specific `ActionMetric.drilldown` analysis (e.g. navigations are not the only backend interactions)

### Fixed
- Reduce overhead of `JavascriptW3cPerformanceTimeline`.

## [3.25.0] - 2023-11-09
[3.25.0]: https://github.com/atlassian/jira-actions/compare/release-3.24.0...release-3.25.0

### Added
- Add `ActionMetric.toBackendTimeSlots()`. Aid with [JPERF-1409].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ data class ActionMetric @Deprecated("Use ActionMetric.Builder instead.") constru

val end: Instant = start + duration

/**
* @since 3.25.0
*/
@Deprecated("This method is misplaced. It overly relies on details of response-thread-plugin. It also assumes that only navigations matter for backend timeslots.")
fun toBackendTimeSlots(): List<BackendTimeSlot> {
return drilldown
?.navigations
Expand All @@ -50,7 +54,7 @@ data class ActionMetric @Deprecated("Use ActionMetric.Builder instead.") constru
?: throw Exception("No threadId in $this, so we cannot map it to a backend timeslot"),
nodeId = resource
.serverTiming
.find { it.name == "nodeId" }
.find { it.name == "clusterNodeId" }
?.description
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package com.atlassian.performance.tools.jiraactions.api
import java.time.Duration
import java.time.Instant

/**
* @since 3.25.0
*/
class BackendTimeSlot internal constructor(
val start: Instant,
val end: Instant,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,26 @@ import org.openqa.selenium.JavascriptExecutor

internal fun getJsNavigationsPerformance(jsExecutor: JavascriptExecutor): List<PerformanceNavigationTiming> {
val jsNavigations = jsExecutor.executeScript("return window.performance.getEntriesByType(\"navigation\");")
return parseNavigations(jsNavigations, jsExecutor)
return parseNavigations(jsNavigations)
}

private fun parseNavigations(
jsNavigations: Any?,
jsExecutor: JavascriptExecutor
jsNavigations: Any?
): List<PerformanceNavigationTiming> {
if (jsNavigations !is List<*>) {
throw Exception("Unexpected non-list JavaScript value: $jsNavigations")
}
return jsNavigations.map { parsePerformanceNavigationTiming(it, jsExecutor) }
return jsNavigations.map { parsePerformanceNavigationTiming(it) }
}

private fun parsePerformanceNavigationTiming(
map: Any?,
jsExecutor: JavascriptExecutor
map: Any?
): PerformanceNavigationTiming {
if (map !is Map<*, *>) {
throw Exception("Unexpected non-map JavaScript value: $map")
}
return PerformanceNavigationTiming(
resource = parsePerformanceResourceTiming(map, jsExecutor),
resource = parsePerformanceResourceTiming(map),
unloadEventStart = parseTimestamp(map["unloadEventStart"]),
unloadEventEnd = parseTimestamp(map["unloadEventEnd"]),
domInteractive = parseTimestamp(map["domInteractive"]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,23 @@ import com.atlassian.performance.tools.jiraactions.api.w3c.PerformanceEntry
import com.atlassian.performance.tools.jiraactions.api.w3c.PerformanceResourceTiming
import com.atlassian.performance.tools.jiraactions.api.w3c.PerformanceServerTiming
import org.openqa.selenium.JavascriptExecutor
import java.time.Duration

internal fun getJsResourcesPerformance(jsExecutor: JavascriptExecutor): List<PerformanceResourceTiming> {
val jsResources = jsExecutor.executeScript("return window.performance.getEntriesByType(\"resource\");")
return parseResources(jsResources, jsExecutor)
return parseResources(jsResources)
}

private fun parseResources(
jsResources: Any,
jsExecutor: JavascriptExecutor
jsResources: Any
): List<PerformanceResourceTiming> {
if (jsResources !is List<*>) {
throw Exception("Unexpected non-list JavaScript value: $jsResources")
}
return jsResources.map { parsePerformanceResourceTiming(it, jsExecutor) }
return jsResources.map { parsePerformanceResourceTiming(it) }
}

internal fun parsePerformanceResourceTiming(
map: Any?,
jsExecutor: JavascriptExecutor
map: Any?
): PerformanceResourceTiming {
if (map !is Map<*, *>) {
throw Exception("Unexpected non-map JavaScript value: $map")
Expand All @@ -47,7 +44,7 @@ internal fun parsePerformanceResourceTiming(
transferSize = map["transferSize"] as Long,
encodedBodySize = map["encodedBodySize"] as Long,
decodedBodySize = map["decodedBodySize"] as Long,
serverTiming = map["serverTiming"]?.let { parseServerTimings(it, jsExecutor) }
serverTiming = map["serverTiming"]?.let { parseServerTimings(it) }
)
}

Expand All @@ -66,29 +63,12 @@ private fun parsePerformanceEntry(
}

private fun parseServerTimings(
jsServerTimings: Any,
jsExecutor: JavascriptExecutor
jsServerTimings: Any
): List<PerformanceServerTiming> {
if (jsServerTimings !is List<*>) {
throw Exception("Unexpected non-list JavaScript value: $jsServerTimings")
}
val result = jsServerTimings.map { parseServerTiming(it) }.toMutableList()
val responseHeaders = getResponseHeaders(jsExecutor)
val nodeId = responseHeaders["x-anodeid"]
addAttribute(result, "nodeId", nodeId)
return result
}

private fun addAttribute(result: MutableList<PerformanceServerTiming>, attributeName: String, attributeValue: String?) {
if (attributeValue != null) {
result.add(
PerformanceServerTiming(
name = attributeName,
duration = Duration.ZERO,
description = attributeValue
)
)
}
return jsServerTimings.map { parseServerTiming(it) }.toMutableList()
}

private fun parseServerTiming(
Expand All @@ -103,25 +83,3 @@ private fun parseServerTiming(
description = map["description"] as String
)
}

private fun getResponseHeaders(jsExecutor: JavascriptExecutor): Map<String, String?> {
val jsResult = jsExecutor.executeScript(
"""
var r = new XMLHttpRequest();
r.open('HEAD', window.location, false);
r.send(null);
var headersString = r.getAllResponseHeaders();
const arr = headersString.trim().split(/[\r\n]+/);
const headerMap = {};
arr.forEach((line) => {
const parts = line.split(": ");
const header = parts.shift();
const value = parts.join(": ");
headerMap[header] = value;
});
return headerMap;
""".trimIndent()
)
@Suppress("UNCHECKED_CAST")
return (jsResult as Map<String, String>).mapKeys { it.key.toLowerCase() }
}
Loading