-
Notifications
You must be signed in to change notification settings - Fork 29
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
misc: tweak metrics to support service-level benchmarks #908
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"id": "73448090-7b5b-4c5a-875a-730efacacf9d", | ||
"type": "misc", | ||
"description": "Tweak metrics to better support service-level benchmarks", | ||
"issues": [ | ||
"awslabs/aws-sdk-kotlin#968" | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package aws.smithy.kotlin.runtime.http.engine | ||
|
||
import aws.smithy.kotlin.runtime.InternalApi | ||
import aws.smithy.kotlin.runtime.util.AttributeKey | ||
import kotlin.time.Duration | ||
|
||
@InternalApi | ||
public object EngineAttributes { | ||
public val TimeToFirstByte: AttributeKey<Duration> = AttributeKey("TimeToFirstByte") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fix: docs |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,11 +9,13 @@ import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext | |
import aws.smithy.kotlin.runtime.client.ProtocolResponseInterceptorContext | ||
import aws.smithy.kotlin.runtime.client.RequestInterceptorContext | ||
import aws.smithy.kotlin.runtime.client.ResponseInterceptorContext | ||
import aws.smithy.kotlin.runtime.http.engine.EngineAttributes | ||
import aws.smithy.kotlin.runtime.http.operation.OperationMetrics | ||
import aws.smithy.kotlin.runtime.http.request.HttpRequest | ||
import aws.smithy.kotlin.runtime.http.response.HttpResponse | ||
import aws.smithy.kotlin.runtime.telemetry.metrics.recordSeconds | ||
import aws.smithy.kotlin.runtime.util.attributesOf | ||
import aws.smithy.kotlin.runtime.util.get | ||
import aws.smithy.kotlin.runtime.util.merge | ||
import aws.smithy.kotlin.runtime.util.mutableAttributesOf | ||
import kotlin.time.ExperimentalTime | ||
|
@@ -39,7 +41,7 @@ internal class OperationTelemetryInterceptor( | |
private var callStart: TimeMark? = null | ||
private var serializeStart: TimeMark? = null | ||
private var deserializeStart: TimeMark? = null | ||
private var transmitStart: TimeMark? = null | ||
private var attemptStart: TimeMark? = null | ||
private var attempts = 0 | ||
|
||
private val perRpcAttributes = attributesOf { | ||
|
@@ -94,6 +96,13 @@ internal class OperationTelemetryInterceptor( | |
if (attempts > 1) { | ||
metrics.rpcRetryCount.add(1L, perRpcAttributes, metrics.provider.contextManager.current()) | ||
} | ||
|
||
val attemptDuration = attemptStart?.elapsedNow() ?: return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This now includes signing and other things right? The current description of the metric was accurate for what it measured: Can you first explain why this is needed/better? Just want to understand the motivation for the change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this now includes identity resolution, endpoint resolution, and signing. The inclusion of EP resolution and signing will likely have minimal impact to the metric but identity resolution is a potentially-expensive operation since it may involve local IO or calls to remote services. Given that the intent is to measure the SDK-added overhead and that identity resolution, EP resolution, and signing are per-attempt, we'd need to know the duration of the entire attempt. I'll update the metric documentation. |
||
metrics.rpcAttemptDuration.recordSeconds(attemptDuration, perRpcAttributes, metrics.provider.contextManager.current()) | ||
|
||
context.executionContext.getOrNull(EngineAttributes.TimeToFirstByte)?.let { ttfb -> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Should we clear this after reading it? e.g. what if we retry and it fails before recording TTFB? We'll be using the first TTFB value. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right we may have an issue if we fail to record TTFB on a retry. We certainly can clear it after reading it, although this makes me wonder if there's a new set of execution context which is scoped to a single attempt and should be cleared when an attempt is ended. What would we think about a new optional scope for attributes: interface MutableAttributes : Attributes {
// ...existing set, remove, and computeIfAbsent methods...
operator fun <T : Any> set(scope: AttributeScope, key: AttributeKey<T>, value: T)
fun <T : Any> removeAll(scope: AttributeScope)
} Get operations would find an attribute regardless of its scope but There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm maybe. Seems like it may complicate some things but wouldn't know until I tried. Also raises questions like what attributes are visible? If I set an attribute in one scope but not another are they visible to other scopes? Are they independent (i.e. can they shadow each other)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, this is likely too complicated without a deeper design. I'll just remove it upon reading. |
||
metrics.rpcAttemptOverheadDuration.recordSeconds(attemptDuration - ttfb, perRpcAttributes) | ||
} | ||
} | ||
|
||
override fun readBeforeDeserialization(context: ProtocolResponseInterceptorContext<Any, HttpRequest, HttpResponse>) { | ||
|
@@ -105,12 +114,7 @@ internal class OperationTelemetryInterceptor( | |
metrics.deserializationDuration.recordSeconds(deserializeDuration, perRpcAttributes, metrics.provider.contextManager.current()) | ||
} | ||
|
||
override fun readBeforeTransmit(context: ProtocolRequestInterceptorContext<Any, HttpRequest>) { | ||
transmitStart = timeSource.markNow() | ||
} | ||
|
||
override fun readAfterTransmit(context: ProtocolResponseInterceptorContext<Any, HttpRequest, HttpResponse>) { | ||
val serviceCallDuration = transmitStart?.elapsedNow() ?: return | ||
metrics.rpcAttemptDuration.recordSeconds(serviceCallDuration, perRpcAttributes, metrics.provider.contextManager.current()) | ||
override fun readBeforeAttempt(context: ProtocolRequestInterceptorContext<Any, HttpRequest>) { | ||
attemptStart = timeSource.markNow() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correctness: Shouldn't this be after sending the body? If there is no body this is fine, we should be able to detect that though and either set it here or in
responseBodyEnd
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little confused about this comment,
requestTimeEnd
is set in both places:requestHeadersEnd
if there is no body, andrequestBodyEnd
if there is a body, so I think it's correct?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're right, I just missed it in first review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes what confused me is that this entire file has the events out-of-order (i.e
requestHeadersEnd
is defined afterrequestBodyStart
even though it occurs before in the chain of events). It could be worth reordering these to match the order shown in the OkHttp docs.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, that would make it easier to parse this file. I can submit a mini-PR after this one gets merged.