Skip to content

Commit bf03c35

Browse files
committed
wip: runs end-to-end but does not capture metrics into hierarchical context; suspect trace spans are not being properly activated
1 parent b475dfd commit bf03c35

File tree

9 files changed

+330
-125
lines changed

9 files changed

+330
-125
lines changed

runtime/observability/telemetry-api/common/src/aws/smithy/kotlin/runtime/telemetry/trace/CoroutineContextTraceExt.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import aws.smithy.kotlin.runtime.ExperimentalApi
99
import aws.smithy.kotlin.runtime.InternalApi
1010
import aws.smithy.kotlin.runtime.collections.Attributes
1111
import aws.smithy.kotlin.runtime.collections.emptyAttributes
12-
import aws.smithy.kotlin.runtime.operation.ExecutionContext
1312
import aws.smithy.kotlin.runtime.telemetry.TelemetryProviderContext
1413
import aws.smithy.kotlin.runtime.telemetry.context.TelemetryContextElement
1514
import aws.smithy.kotlin.runtime.telemetry.context.telemetryContext
@@ -45,14 +44,15 @@ public val CoroutineContext.traceSpan: TraceSpan?
4544
* created span set.
4645
*/
4746
@InternalApi
47+
@OptIn(ExperimentalApi::class)
4848
public suspend inline fun <R> Tracer.withSpan(
4949
name: String,
5050
initialAttributes: Attributes = emptyAttributes(),
5151
spanKind: SpanKind = SpanKind.INTERNAL,
5252
context: CoroutineContext = EmptyCoroutineContext,
5353
crossinline block: suspend CoroutineScope.(span: TraceSpan) -> R,
5454
): R {
55-
val parent = coroutineContext.telemetryContext
55+
val parent = coroutineContext.telemetryContext ?: coroutineContext.telemetryProvider.contextManager.current(coroutineContext)
5656
val span = createSpan(name, initialAttributes, spanKind, parent)
5757
return withSpan(span, context, block)
5858
}

runtime/observability/telemetry-provider-ism/api/telemetry-provider-ism.api

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
public final class aws/smithy/kotlin/runtime/telemetry/ism/IsmContextManager : aws/smithy/kotlin/runtime/telemetry/context/ContextManager {
2-
public static final field Companion Laws/smithy/kotlin/runtime/telemetry/ism/IsmContextManager$Companion;
3-
public fun current (Lkotlin/coroutines/CoroutineContext;)Laws/smithy/kotlin/runtime/telemetry/context/Context;
4-
}
5-
6-
public final class aws/smithy/kotlin/runtime/telemetry/ism/IsmContextManager$Companion {
7-
public final fun createWithScopeListener ()Lkotlin/Pair;
1+
public abstract interface class aws/smithy/kotlin/runtime/telemetry/ism/IsmMetricSink {
2+
public abstract fun onInvocationComplete (Laws/smithy/kotlin/runtime/telemetry/ism/OperationMetrics;)V
83
}
94

105
public final class aws/smithy/kotlin/runtime/telemetry/ism/IsmMetricsProvider : aws/smithy/kotlin/runtime/telemetry/metrics/MeterProvider {
@@ -18,9 +13,23 @@ public final class aws/smithy/kotlin/runtime/telemetry/ism/IsmMetricsProviderKt
1813
public static final fun ScopeMetrics (Ljava/util/Map;Ljava/util/Map;)Laws/smithy/kotlin/runtime/telemetry/ism/ScopeMetrics;
1914
}
2015

16+
public final class aws/smithy/kotlin/runtime/telemetry/ism/IsmTelemetryProvider : aws/smithy/kotlin/runtime/telemetry/TelemetryProvider {
17+
public fun <init> (Laws/smithy/kotlin/runtime/telemetry/ism/IsmMetricSink;Laws/smithy/kotlin/runtime/telemetry/logging/LoggerProvider;)V
18+
public synthetic fun <init> (Laws/smithy/kotlin/runtime/telemetry/ism/IsmMetricSink;Laws/smithy/kotlin/runtime/telemetry/logging/LoggerProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
19+
public fun getContextManager ()Laws/smithy/kotlin/runtime/telemetry/context/ContextManager;
20+
public fun getLoggerProvider ()Laws/smithy/kotlin/runtime/telemetry/logging/LoggerProvider;
21+
public fun getMeterProvider ()Laws/smithy/kotlin/runtime/telemetry/metrics/MeterProvider;
22+
public fun getTracerProvider ()Laws/smithy/kotlin/runtime/telemetry/trace/TracerProvider;
23+
}
24+
25+
public final class aws/smithy/kotlin/runtime/telemetry/ism/MetricHoldersKt {
26+
public static final fun MetricRecord (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;Laws/smithy/kotlin/runtime/collections/Attributes;Laws/smithy/kotlin/runtime/time/Instant;)Laws/smithy/kotlin/runtime/telemetry/ism/MetricRecord;
27+
public static final fun OperationMetrics (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;)Laws/smithy/kotlin/runtime/telemetry/ism/OperationMetrics;
28+
public static final fun ScopeMetrics (Ljava/util/List;Ljava/util/Map;)Laws/smithy/kotlin/runtime/telemetry/ism/ScopeMetrics;
29+
}
30+
2131
public abstract interface class aws/smithy/kotlin/runtime/telemetry/ism/MetricRecord {
2232
public abstract fun getAttributes ()Laws/smithy/kotlin/runtime/collections/Attributes;
23-
public abstract fun getContext ()Laws/smithy/kotlin/runtime/telemetry/context/Context;
2433
public abstract fun getDescription ()Ljava/lang/String;
2534
public abstract fun getName ()Ljava/lang/String;
2635
public abstract fun getTimestamp ()Laws/smithy/kotlin/runtime/time/Instant;
@@ -40,11 +49,6 @@ public abstract interface class aws/smithy/kotlin/runtime/telemetry/ism/Operatio
4049

4150
public abstract interface class aws/smithy/kotlin/runtime/telemetry/ism/ScopeMetrics {
4251
public abstract fun getChildScopes ()Ljava/util/Map;
43-
public abstract fun getRecords ()Ljava/util/Map;
44-
}
45-
46-
public abstract interface class aws/smithy/kotlin/runtime/telemetry/ism/SpanListener {
47-
public abstract fun onCloseSpan (Laws/smithy/kotlin/runtime/telemetry/context/Context;)V
48-
public abstract fun onNewSpan (Laws/smithy/kotlin/runtime/telemetry/context/Context;Ljava/lang/String;Laws/smithy/kotlin/runtime/collections/Attributes;)Laws/smithy/kotlin/runtime/telemetry/context/Context;
52+
public abstract fun getRecords ()Ljava/util/List;
4953
}
5054

runtime/observability/telemetry-provider-ism/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ description = "Telemetry provider for invocation-scoped metrics"
66
extra["displayName"] = "Smithy :: Kotlin :: Observability :: Invocation-scoped Metrics Provider"
77
extra["moduleName"] = "aws.smithy.kotlin.runtime.telemetry.ism"
88

9-
apply(plugin = "kotlinx-atomicfu")
9+
apply(plugin = "org.jetbrains.kotlinx.atomicfu")
1010

1111
kotlin {
1212
sourceSets {
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package aws.smithy.kotlin.runtime.telemetry.ism
2+
3+
import aws.smithy.kotlin.runtime.telemetry.context.Context
4+
import aws.smithy.kotlin.runtime.telemetry.context.Scope
5+
import aws.smithy.kotlin.runtime.telemetry.trace.SpanContext
6+
import kotlin.random.Random
7+
8+
internal sealed class HierarchicalContext(
9+
val name: String,
10+
val parent: Context?,
11+
val spanContext: SpanContext,
12+
) : Context {
13+
constructor(name: String, parent: Context?) : this(
14+
name,
15+
parent,
16+
parent?.spanContext()?.childSpan() ?: SpanContext.Invalid,
17+
)
18+
19+
open val records: MutableList<MetricRecord<*>> = mutableListOf()
20+
open val children: MutableMap<String, ChildContext> = mutableMapOf()
21+
22+
override fun makeCurrent(): Scope = IsmScope(this)
23+
}
24+
25+
internal class RootContext : HierarchicalContext("root", null, IsmSpanContext.fromScratch()) {
26+
override val records: MutableList<MetricRecord<*>>
27+
get() = mutableListOf()
28+
29+
override val children: MutableMap<String, ChildContext>
30+
get() = mutableMapOf()
31+
}
32+
33+
internal class OperationContext(
34+
name: String,
35+
rootContext: RootContext,
36+
val service: String,
37+
val operation: String,
38+
val sdkInvocationId: String,
39+
) : HierarchicalContext(name, rootContext)
40+
41+
internal class ChildContext(name: String, parent: HierarchicalContext) : HierarchicalContext(name, parent)
42+
43+
internal class OtherContext(name: String, parent: Context?) : HierarchicalContext(name, parent)
44+
45+
private class IsmScope(val context: Context) : Scope {
46+
override fun close() = Unit
47+
}
48+
49+
private data class IsmSpanContext(
50+
override val traceId: String,
51+
override val spanId: String,
52+
override val isRemote: Boolean,
53+
) : SpanContext {
54+
companion object {
55+
@OptIn(ExperimentalStdlibApi::class)
56+
private fun nextHexString(byteLength: Int) = Random.nextBytes(byteLength).toHexString()
57+
58+
private fun nextSpanId() = nextHexString(8)
59+
private fun nextTraceId() = nextHexString(16)
60+
61+
fun fromScratch() = IsmSpanContext(nextTraceId(), nextSpanId(), false)
62+
63+
private val zeroChar = setOf(' ', '0')
64+
fun isValid(id: String) = id.any { it !in zeroChar }
65+
}
66+
67+
override val isValid = isValid(traceId) && isValid(spanId)
68+
69+
fun childSpan() = IsmSpanContext(traceId, nextSpanId(), isRemote)
70+
}
71+
72+
private fun Context.spanContext() = when (this) {
73+
is HierarchicalContext -> spanContext
74+
else -> SpanContext.Invalid
75+
}
76+
77+
private fun SpanContext.childSpan() = when (this) {
78+
is IsmSpanContext -> childSpan()
79+
else -> SpanContext.Invalid
80+
}

runtime/observability/telemetry-provider-ism/common/src/aws/smithy/kotlin/runtime/telemetry/ism/IsmContextManager.kt

Lines changed: 57 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,80 +4,85 @@
44
*/
55
package aws.smithy.kotlin.runtime.telemetry.ism
66

7+
import aws.smithy.kotlin.runtime.ExperimentalApi
78
import aws.smithy.kotlin.runtime.collections.Attributes
89
import aws.smithy.kotlin.runtime.http.operation.OperationAttributes
910
import aws.smithy.kotlin.runtime.telemetry.context.Context
1011
import aws.smithy.kotlin.runtime.telemetry.context.ContextManager
11-
import aws.smithy.kotlin.runtime.telemetry.context.Scope
1212
import aws.smithy.kotlin.runtime.telemetry.context.telemetryContext
1313
import kotlin.coroutines.CoroutineContext
1414

15-
public interface SpanListener {
16-
public fun onNewSpan(parentContext: Context?, name: String, attributes: Attributes): Context
17-
public fun onCloseSpan(context: Context)
15+
internal interface MetricListener {
16+
fun onMetrics(context: Context, metrics: MetricRecord<*>)
1817
}
1918

20-
public class IsmContextManager private constructor() : ContextManager {
21-
public companion object {
22-
public fun createWithScopeListener(): Pair<IsmContextManager, SpanListener> {
23-
val manager = IsmContextManager()
24-
val listener = object : SpanListener {
25-
override fun onNewSpan(parentContext: Context?, name: String, attributes: Attributes) =
26-
manager.onNewSpan(parentContext, name, attributes)
19+
internal interface SpanListener {
20+
fun onNewSpan(parentContext: Context?, name: String, attributes: Attributes): Context
21+
fun onCloseSpan(context: Context)
22+
}
23+
24+
@OptIn(ExperimentalApi::class)
25+
internal class IsmContextManager internal constructor(private val sink: IsmMetricSink) : ContextManager {
26+
private val rootContext = RootContext()
2727

28-
override fun onCloseSpan(context: Context) = manager.onCloseSpan(context)
28+
override fun current(ctx: CoroutineContext): Context =
29+
ctx.telemetryContext.takeIf { it != Context.None } ?: rootContext
30+
31+
internal val metricListener = object : MetricListener {
32+
override fun onMetrics(context: Context, metrics: MetricRecord<*>) {
33+
println("Listener received metrics on $context: $metrics")
34+
when (context) {
35+
is OperationContext -> context.records += metrics
36+
is ChildContext -> context.records += metrics
2937
}
30-
return manager to listener
3138
}
3239
}
3340

34-
private val rootContext = object : HierarchicalContext(null) { }
35-
36-
override fun current(ctx: CoroutineContext): Context = ctx.telemetryContext ?: rootContext
41+
internal val spanListener = object : SpanListener {
42+
override fun onNewSpan(parentContext: Context?, name: String, attributes: Attributes): Context {
43+
println("Listener received new span on $parentContext: $name")
44+
return when (parentContext) {
45+
null, rootContext -> {
46+
val service = attributes.getOrNull(OperationAttributes.RpcService)
47+
val operation = attributes.getOrNull(OperationAttributes.RpcOperation)
48+
val sdkInvocationId = attributes.getOrNull(OperationAttributes.AwsInvocationId)
3749

38-
private fun onNewSpan(parentContext: Context?, name: String, attributes: Attributes): Context =
39-
when (parentContext) {
40-
rootContext -> {
41-
val service = attributes.getOrNull(OperationAttributes.RpcService)
42-
val operation = attributes.getOrNull(OperationAttributes.RpcOperation)
43-
val sdkInvocationId = attributes.getOrNull(OperationAttributes.AwsInvocationId)
44-
if (service != null && operation != null && sdkInvocationId != null) {
45-
OperationContext(service, operation, sdkInvocationId)
46-
} else {
47-
OtherContext(parentContext)
50+
if (service == null || operation == null || sdkInvocationId == null) {
51+
OtherContext(name, parentContext)
52+
} else {
53+
OperationContext(name, rootContext, service, operation, sdkInvocationId)
54+
}
4855
}
49-
}
5056

51-
is OperationContext, is ChildContext -> ChildContext(parentContext)
57+
is HierarchicalContext -> ChildContext(name, parentContext)
5258

53-
else -> OtherContext(parentContext)
59+
else -> OtherContext(name, parentContext)
60+
}
5461
}
5562

56-
private fun onCloseSpan(context: Context) = Unit
57-
58-
private abstract inner class HierarchicalContext(val parent: Context?) : Context {
59-
override fun makeCurrent(): Scope {
60-
return IsmScope(this)
63+
override fun onCloseSpan(context: Context) {
64+
when (context) {
65+
is OperationContext -> publish(context)
66+
is ChildContext -> (context.parent as? HierarchicalContext)?.let { parentContext ->
67+
parentContext.children += context.name to context
68+
}
69+
}
6170
}
6271
}
6372

64-
private inner class OperationContext(
65-
val service: String,
66-
val operation: String,
67-
val sdkInvocationId: String,
68-
val records: MutableList<MetricRecord<*>> = mutableListOf(),
69-
val childScopes: MutableMap<String, ScopeMetrics> = mutableMapOf(),
70-
) : HierarchicalContext(rootContext)
71-
72-
private inner class ChildContext(
73-
parent: Context,
74-
val records: MutableList<MetricRecord<*>> = mutableListOf(),
75-
val childScopes: MutableMap<String, ScopeMetrics> = mutableMapOf(),
76-
) : HierarchicalContext(parent)
77-
78-
private inner class OtherContext(parent: Context?) : HierarchicalContext(parent)
79-
80-
private inner class IsmScope(val context: Context) : Scope {
81-
override fun close() = Unit
73+
private fun publish(context: OperationContext) {
74+
val opMetrics = OperationMetrics(
75+
context.service,
76+
context.operation,
77+
context.sdkInvocationId,
78+
context.records.toList(),
79+
context.children.mapValues { (_, child) -> child.toScopeMetrics() }
80+
)
81+
sink.onInvocationComplete(opMetrics)
8282
}
8383
}
84+
85+
private fun ChildContext.toScopeMetrics(): ScopeMetrics = ScopeMetrics(
86+
records.toList(),
87+
children.mapValues { (_, child) -> child.toScopeMetrics() }
88+
)

0 commit comments

Comments
 (0)