-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
JPERF-1454 Move rainbow to API, with a better name
TDD: refactor
- Loading branch information
1 parent
bb5f851
commit 4f8b734
Showing
7 changed files
with
395 additions
and
307 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
...main/kotlin/com/atlassian/performance/tools/report/api/drilldown/ActionMetricExplainer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.atlassian.performance.tools.report.api.drilldown | ||
|
||
import com.atlassian.performance.tools.jiraactions.api.ActionMetric | ||
import com.atlassian.performance.tools.report.drilldown.TimeTrain | ||
import java.time.Instant | ||
|
||
object ActionMetricExplainer { | ||
fun explainDuration(metric: ActionMetric): DurationDrilldown { | ||
val drilldown = metric.drilldown!! | ||
val nav = drilldown.navigations.single() | ||
val timeOrigin = drilldown.timeOrigin!! | ||
return with(nav.resource) { | ||
val train = TimeTrain(metric.start, metric.end, timeOrigin) | ||
val lastResource = drilldown.resources | ||
.map { timeOrigin + it.responseEnd } | ||
.filter { it < metric.end } | ||
.max() ?: Instant.MIN | ||
|
||
DurationDrilldown.Builder(metric.duration) | ||
.preNav(train.jumpOff(redirectStart)) | ||
.redirect(train.jumpOff(redirectEnd)) | ||
.preWorker(train.jumpOff(workerStart)) | ||
.serviceWorkerInit(train.jumpOff(fetchStart)) | ||
.fetchAndCache(train.jumpOff(domainLookupStart)) | ||
.dns(train.jumpOff(domainLookupEnd)) | ||
.preConnect(train.jumpOff(connectStart)) | ||
.tcp(train.jumpOff(connectEnd)) | ||
.preRequest(train.jumpOff(requestStart)) | ||
.request(train.jumpOff(responseStart)) | ||
.response(train.jumpOff(responseEnd)) | ||
.domProcessing(train.jumpOff(nav.domComplete)) | ||
.preLoad(train.jumpOff(nav.loadEventStart)) | ||
.load(train.jumpOff(nav.loadEventEnd)) | ||
.lateResources(train.jumpOff(lastResource)) | ||
.excessProcessing(train.jumpOff(metric.end)) | ||
.build() | ||
} | ||
} | ||
} |
170 changes: 170 additions & 0 deletions
170
src/main/kotlin/com/atlassian/performance/tools/report/api/drilldown/DurationDrilldown.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
package com.atlassian.performance.tools.report.api.drilldown | ||
|
||
import com.atlassian.performance.tools.jiraactions.api.ActionMetric | ||
import java.time.Duration | ||
|
||
/** | ||
* Represents [ActionMetric.duration] split into linear segments based on [ActionMetric.drilldown]. | ||
*/ | ||
class DurationDrilldown private constructor( | ||
/** | ||
* Everything that happened before current navigation started, including, but not limited to: | ||
* - delay between measurement start and browser starting to load the page | ||
* - cross-origin redirects | ||
* - previous navigations (as within one [ActionMetric] there can be multiple navigations) | ||
*/ | ||
val preNav: Duration, | ||
val redirect: Duration, | ||
val preWorker: Duration, | ||
val serviceWorkerInit: Duration, | ||
val fetchAndCache: Duration, | ||
val dns: Duration, | ||
val preConnect: Duration, | ||
val tcp: Duration, | ||
val preRequest: Duration, | ||
val request: Duration, | ||
val response: Duration, | ||
val domProcessing: Duration, | ||
val preLoad: Duration, | ||
val load: Duration, | ||
/** | ||
* Resources (XHR, JS, images) still downloading after [load]. | ||
*/ | ||
val lateResources: Duration, | ||
/** | ||
* Everything that happened after [lateResources]. That is no-network activity, including, but not limited to: | ||
* - JavaScript execution | ||
* - rendering | ||
*/ | ||
val excessProcessing: Duration, | ||
/** | ||
* @return [ActionMetric.duration] | ||
*/ | ||
val total: Duration | ||
) { | ||
|
||
/** | ||
* All the segments should add up to [total] | ||
* @return difference between the sum of segments and the [total], should be zero | ||
*/ | ||
val unexplained: Duration = total | ||
.minus(preNav) | ||
.minus(redirect) | ||
.minus(preWorker) | ||
.minus(serviceWorkerInit) | ||
.minus(fetchAndCache) | ||
.minus(dns) | ||
.minus(preConnect) | ||
.minus(tcp) | ||
.minus(preRequest) | ||
.minus(request) | ||
.minus(response) | ||
.minus(domProcessing) | ||
.minus(lateResources) | ||
.minus(excessProcessing) | ||
.minus(preLoad) | ||
.minus(load) | ||
|
||
init { | ||
assert(preNav.isNegative.not()) { "preNav duration cannot be negative" } | ||
assert(redirect.isNegative.not()) { "redirect duration cannot be negative" } | ||
assert(preWorker.isNegative.not()) { "preWorker duration cannot be negative" } | ||
assert(serviceWorkerInit.isNegative.not()) { "serviceWorkerInit duration cannot be negative" } | ||
assert(fetchAndCache.isNegative.not()) { "fetchAndCache duration cannot be negative" } | ||
assert(dns.isNegative.not()) { "dns duration cannot be negative" } | ||
assert(preConnect.isNegative.not()) { "preConnect duration cannot be negative" } | ||
assert(tcp.isNegative.not()) { "tcp duration cannot be negative" } | ||
assert(preRequest.isNegative.not()) { "preRequest duration cannot be negative" } | ||
assert(request.isNegative.not()) { "request duration cannot be negative" } | ||
assert(response.isNegative.not()) { "response duration cannot be negative" } | ||
assert(domProcessing.isNegative.not()) { "processing duration cannot be negative" } | ||
assert(preLoad.isNegative.not()) { "preLoad duration cannot be negative" } | ||
assert(load.isNegative.not()) { "load duration cannot be negative" } | ||
assert(lateResources.isNegative.not()) { "lateResources cannot be negative" } | ||
assert(excessProcessing.isNegative.not()) { "excessProcessing cannot be negative" } | ||
assert(total.isNegative.not()) { "total duration cannot be negative" } | ||
} | ||
|
||
override fun toString(): String { | ||
return "DurationDrilldown(" + | ||
"preNav=$preNav, " + | ||
"redirect=$redirect, " + | ||
"preWorker=$preWorker, " + | ||
"serviceWorkerInit=$serviceWorkerInit, " + | ||
"fetchAndCache=$fetchAndCache, " + | ||
"dns=$dns, " + | ||
"preConnect=$preConnect, " + | ||
"tcp=$tcp, " + | ||
"preRequest=$preRequest, " + | ||
"request=$request, " + | ||
"response=$response, " + | ||
"domProcessing=$domProcessing, " + | ||
"preLoad=$preLoad, " + | ||
"load=$load, " + | ||
"lateResources=$lateResources, " + | ||
"excessProcessing=$excessProcessing, " + | ||
"total=$total, " + | ||
"unexplained=$unexplained" + | ||
")" | ||
} | ||
|
||
class Builder( | ||
private var total: Duration | ||
) { | ||
private var preNav: Duration = Duration.ZERO | ||
private var redirect: Duration = Duration.ZERO | ||
private var preWorker: Duration = Duration.ZERO | ||
private var serviceWorkerInit: Duration = Duration.ZERO | ||
private var fetchAndCache: Duration = Duration.ZERO | ||
private var dns: Duration = Duration.ZERO | ||
private var preConnect: Duration = Duration.ZERO | ||
private var tcp: Duration = Duration.ZERO | ||
private var preRequest: Duration = Duration.ZERO | ||
private var request: Duration = Duration.ZERO | ||
private var response: Duration = Duration.ZERO | ||
private var domProcessing: Duration = Duration.ZERO | ||
private var preLoad: Duration = Duration.ZERO | ||
private var load: Duration = Duration.ZERO | ||
private var lateResources: Duration = Duration.ZERO | ||
private var excessProcessing: Duration = Duration.ZERO | ||
|
||
fun preNav(preNav: Duration) = apply { this.preNav = preNav } | ||
fun redirect(redirect: Duration) = apply { this.redirect = redirect } | ||
fun preWorker(preWorker: Duration) = apply { this.preWorker = preWorker } | ||
fun serviceWorkerInit(serviceWorkerInit: Duration) = apply { this.serviceWorkerInit = serviceWorkerInit } | ||
fun fetchAndCache(fetchAndCache: Duration) = apply { this.fetchAndCache = fetchAndCache } | ||
fun dns(dns: Duration) = apply { this.dns = dns } | ||
fun preConnect(preConnect: Duration) = apply { this.preConnect = preConnect } | ||
fun tcp(tcp: Duration) = apply { this.tcp = tcp } | ||
fun preRequest(preRequest: Duration) = apply { this.preRequest = preRequest } | ||
fun request(request: Duration) = apply { this.request = request } | ||
fun response(response: Duration) = apply { this.response = response } | ||
fun domProcessing(processing: Duration) = apply { this.domProcessing = processing } | ||
fun preLoad(preLoad: Duration) = apply { this.preLoad = preLoad } | ||
fun load(load: Duration) = apply { this.load = load } | ||
fun lateResources(lateResources: Duration) = apply { this.lateResources = lateResources } | ||
fun excessProcessing(excessProcessing: Duration) = apply { this.excessProcessing = excessProcessing } | ||
fun total(total: Duration) = apply { this.total = total } | ||
|
||
fun build() = DurationDrilldown( | ||
preNav = preNav, | ||
redirect = redirect, | ||
preWorker = preWorker, | ||
serviceWorkerInit = serviceWorkerInit, | ||
fetchAndCache = fetchAndCache, | ||
dns = dns, | ||
preConnect = preConnect, | ||
tcp = tcp, | ||
preRequest = preRequest, | ||
request = request, | ||
response = response, | ||
domProcessing = domProcessing, | ||
preLoad = preLoad, | ||
load = load, | ||
lateResources = lateResources, | ||
excessProcessing = excessProcessing, | ||
total = total | ||
) | ||
} | ||
} | ||
|
40 changes: 40 additions & 0 deletions
40
src/main/kotlin/com/atlassian/performance/tools/report/drilldown/TimeTrain.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.atlassian.performance.tools.report.drilldown | ||
|
||
import com.atlassian.performance.tools.jiraactions.api.w3c.PerformanceNavigationTiming | ||
import com.atlassian.performance.tools.jiraactions.api.w3c.PerformanceResourceTiming | ||
import java.time.Duration | ||
import java.time.Instant | ||
|
||
internal class TimeTrain( | ||
firstStation: Instant, | ||
private val lastStation: Instant, | ||
private val timeOrigin: Instant | ||
) { | ||
|
||
private var prevStation: Instant = firstStation | ||
|
||
/** | ||
* Jump off at the next station | ||
* | ||
* @return how much time elapsed since the last station, a linear time segment | ||
*/ | ||
fun jumpOff(nextStation: Instant): Duration { | ||
/** | ||
* Some stations are optional, e.g. [PerformanceResourceTiming.workerStart] | ||
* or might not have happened yet, e.g. before [PerformanceNavigationTiming.loadEventStart] | ||
* Some stations are parallel and can come in different order in runtime, | ||
* e.g. last [PerformanceResourceTiming] might come before or after [PerformanceNavigationTiming.loadEventEnd]. | ||
*/ | ||
if (nextStation < prevStation) { | ||
return Duration.ZERO | ||
} | ||
val jumpOffStation = minOf(nextStation, lastStation) | ||
val segment = Duration.between(prevStation, jumpOffStation) | ||
prevStation = jumpOffStation | ||
return segment | ||
} | ||
|
||
fun jumpOff(nextStation: Duration): Duration { | ||
return jumpOff(timeOrigin + nextStation) | ||
} | ||
} |
Oops, something went wrong.