From a2f329a3264c9333ba00f17f860f04d5edf160d0 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 17 Sep 2024 15:45:30 -0700 Subject: [PATCH 1/6] Synchronous flow working --- .../agent/internal/init/SecondEntryPoint.java | 9 + .../keytransaction/KeyTransactionConfig.java | 139 ++++++++++++++ .../KeyTransactionConfigSupplier.java | 93 +++++++++ .../keytransaction/KeyTransactionSampler.java | 176 ++++++++++++++++++ .../KeyTransactionSpanProcessor.java | 116 ++++++++++++ ...yTransactionTelemetryPipelineListener.java | 91 +++++++++ .../KeyTransactionTraceState.java | 75 ++++++++ .../internal/keytransaction/NewResponse.java | 33 ++++ .../keytransaction/SdkConfiguration.java | 39 ++++ .../agent/internal/sampling/Samplers.java | 20 +- .../internal/telemetry/TelemetryClient.java | 7 + .../KeyTransactionConfigTest.java | 54 ++++++ .../KeyTransactionTraceStateTest.java | 37 ++++ .../agent/internal/keytransaction/demo.json | 42 +++++ 14 files changed, 924 insertions(+), 7 deletions(-) create mode 100644 agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfig.java create mode 100644 agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java create mode 100644 agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSampler.java create mode 100644 agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSpanProcessor.java create mode 100644 agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTelemetryPipelineListener.java create mode 100644 agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceState.java create mode 100644 agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/NewResponse.java create mode 100644 agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/SdkConfiguration.java create mode 100644 agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java create mode 100644 agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceStateTest.java create mode 100644 agent/agent-tooling/src/test/resources/com/microsoft/applicationinsights/agent/internal/keytransaction/demo.json diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java index 00fb285ab34..260c1965a20 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java @@ -39,6 +39,8 @@ import com.microsoft.applicationinsights.agent.internal.exporter.AgentSpanExporter; import com.microsoft.applicationinsights.agent.internal.exporter.ExporterUtils; import com.microsoft.applicationinsights.agent.internal.httpclient.LazyHttpClient; +import com.microsoft.applicationinsights.agent.internal.keytransaction.KeyTransactionConfigSupplier; +import com.microsoft.applicationinsights.agent.internal.keytransaction.KeyTransactionSpanProcessor; import com.microsoft.applicationinsights.agent.internal.legacyheaders.AiLegacyHeaderSpanProcessor; import com.microsoft.applicationinsights.agent.internal.processors.ExporterWithLogProcessor; import com.microsoft.applicationinsights.agent.internal.processors.ExporterWithSpanProcessor; @@ -550,6 +552,10 @@ private static SdkTracerProviderBuilder configureTracing( tracerProvider.addSpanProcessor(new AiLegacyHeaderSpanProcessor()); } + if (KeyTransactionConfigSupplier.KEY_TRANSACTIONS_ENABLED) { + tracerProvider.addSpanProcessor(new KeyTransactionSpanProcessor()); + } + return tracerProvider; } @@ -745,6 +751,9 @@ private static void drop( @Override public void afterAutoConfigure(OpenTelemetrySdk sdk) { + + KeyTransactionSpanProcessor.initMeterProvider(sdk.getMeterProvider()); + Runtime.getRuntime() .addShutdownHook( new Thread( diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfig.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfig.java new file mode 100644 index 00000000000..4023349782c --- /dev/null +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfig.java @@ -0,0 +1,139 @@ +package com.microsoft.applicationinsights.agent.internal.keytransaction; + +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import java.io.IOException; +import java.util.List; + +public class KeyTransactionConfig { + + private String name; + private List startCriteria; + private List endCriteria; + + String getName() { + return name; + } + + List getStartCriteria() { + return startCriteria; + } + + List getEndCriteria() { + return endCriteria; + } + + public static KeyTransactionConfig fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject((reader) -> { + KeyTransactionConfig deserializedValue = new KeyTransactionConfig(); + + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + if ("Name".equals(fieldName)) { + deserializedValue.name = reader.getString(); + } else if ("StartCriteria".equals(fieldName)) { + deserializedValue.startCriteria = reader.readArray(Criterion::fromJson); + } else if ("EndCriteria".equals(fieldName)) { + deserializedValue.endCriteria = reader.readArray(Criterion::fromJson); + } else { + reader.skipChildren(); + } + } + + return deserializedValue; + }); + } + + public static boolean matches(Attributes attributes, List criteria) { + for (Criterion criterion : criteria) { + String value = attributes.get(criterion.field); + switch (criterion.operator) { + case EQUALS: + if (value == null || !value.equals(criterion.value)) { + return false; + } + break; + case STARTSWITH: + if (value == null || !value.startsWith(criterion.value)) { + return false; + } + break; + case CONTAINS: + if (value == null || !value.contains(criterion.value)) { + return false; + } + break; + default: + // unexpected operator + return false; + } + } + return true; + } + + // TODO (not for hackathon) expand this to work with non-String attributes + // a bit tricky since Attributes#get(AttributeKey) requires a known type + // (without iterating over all attributes) + public static class Criterion { + private AttributeKey field; + private String value; + private Operator operator; + + // visible for testing + AttributeKey getField() { + return field; + } + + // visible for testing + String getValue() { + return value; + } + + // visible for testing + Operator getOperator() { + return operator; + } + + public static Criterion fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject((reader) -> { + Criterion deserializedValue = new Criterion(); + + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + if ("Field".equals(fieldName)) { + deserializedValue.field = AttributeKey.stringKey(reader.getString()); + } else if ("Operator".equals(fieldName)) { + deserializedValue.operator = Operator.from(reader.getString()); + } else if ("Value".equals(fieldName)) { + deserializedValue.value = reader.getString(); + } else { + reader.skipChildren(); + } + } + + return deserializedValue; + }); + } + } + + public enum Operator { + EQUALS, STARTSWITH, CONTAINS; + + private static Operator from(String value) { + switch (value) { + case "==": + return EQUALS; + case "startswith": + return STARTSWITH; + case "contains": + return CONTAINS; + default: + throw new IllegalStateException("Unexpected operator: " + value); + } + } + } +} diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java new file mode 100644 index 00000000000..5379d1f18aa --- /dev/null +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java @@ -0,0 +1,93 @@ +package com.microsoft.applicationinsights.agent.internal.keytransaction; + +import com.azure.json.JsonProviders; +import java.io.IOException; +import java.util.List; +import java.util.function.Supplier; + +import static java.util.stream.Collectors.toList; + +public class KeyTransactionConfigSupplier implements Supplier> { + + public static final boolean KEY_TRANSACTIONS_ENABLED = true; + + // TODO remove reliance on global + private static final KeyTransactionConfigSupplier instance = new KeyTransactionConfigSupplier(); + +// static { +// instance.set(hardcodedDemo()); +// } + + public static KeyTransactionConfigSupplier getInstance() { + return instance; + } + + private volatile List configs; + + @Override + public List get() { + return configs; + } + + public void set(List configs) { + this.configs = configs; + } + + @SuppressWarnings("unused") + private static List hardcodedDemo() { + List configs; + try { + configs = NewResponse.fromJson(JsonProviders.createReader( + "{\n" + + " \"itemsReceived\": 13,\n" + + " \"itemsAccepted\": 13,\n" + + " \"appId\": null,\n" + + " \"errors\": [],\n" + + " \"sdkConfiguration\": [\n" + + " {\n" + + " \"Key\": \"Transaction\",\n" + + " \"Value\": {\n" + + " \"Name\": \"EarthOrbit\",\n" + + " \"StartCriteria\": [\n" + + " {\n" + + " \"Field\": \"url.path\",\n" + + " \"Operator\": \"==\",\n" + + " \"Value\": \"/earth\"\n" + + " }\n" + + " ],\n" + + " \"EndCriteria\": []\n" + + " }\n" + + " },\n" + + " {\n" + + " \"Key\": \"Transaction\",\n" + + " \"Value\": {\n" + + " \"Name\": \"MarsMission\",\n" + + " \"StartCriteria\": [\n" + + " {\n" + + " \"Field\": \"url.path\",\n" + + " \"Operator\": \"==\",\n" + + " \"Value\": \"/mars\"\n" + + " }\n" + + " ],\n" + + " \"EndCriteria\": [\n" + + " {\n" + + " \"Field\": \"messaging.todo\",\n" + + " \"Operator\": \"==\",\n" + + " \"Value\": \"todo\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + " ]\n" + + "}\n" + )) + .getSdkConfigurations() + .stream() + .map(SdkConfiguration::getValue) + .collect(toList()); + } catch (IOException e) { + throw new IllegalStateException(e); + } + return configs; + } +} diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSampler.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSampler.java new file mode 100644 index 00000000000..2c07081a717 --- /dev/null +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSampler.java @@ -0,0 +1,176 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.microsoft.applicationinsights.agent.internal.keytransaction; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.opentelemetry.sdk.trace.samplers.SamplingDecision; +import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public final class KeyTransactionSampler implements Sampler { + + private final Supplier> configs; + private final Sampler fallback; + + private KeyTransactionSampler(Supplier> configs, Sampler fallback) { + this.configs = configs; + this.fallback = fallback; + } + + public static KeyTransactionSampler create(Supplier> configs, + Sampler fallback) { + return new KeyTransactionSampler(configs, fallback); + } + + @Override + public SamplingResult shouldSample( + Context parentContext, + String traceId, + String name, + SpanKind spanKind, + Attributes attributes, + List parentLinks) { + + SpanContext spanContext = Span.fromContext(parentContext).getSpanContext(); + if (spanContext.isValid() && !spanContext.isRemote()) { + // for now only applying to local root spans + return fallback.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks); + } + + List configs = this.configs.get(); + + Set existingKeyTransactions = + KeyTransactionTraceState.getKeyTransactionStartTimes(spanContext.getTraceState()) + .keySet(); + + List startKeyTransactions = new ArrayList<>(); + List endKeyTransactions = new ArrayList<>(); + for (KeyTransactionConfig config : configs) { + + if (existingKeyTransactions.contains(config.getName())) { + // consider ending it + if (!config.getEndCriteria().isEmpty() && KeyTransactionConfig.matches(attributes, config.getEndCriteria())) { + endKeyTransactions.add(config.getName()); + } + } else { + // consider starting it + if (KeyTransactionConfig.matches(attributes, config.getStartCriteria())) { + startKeyTransactions.add(config.getName()); + // consider ending it right away + if (config.getEndCriteria().isEmpty() + || KeyTransactionConfig.matches(attributes, config.getEndCriteria())) { + endKeyTransactions.add(config.getName()); + } + } + } + } + + // always delegate to fallback sampler to give it a chance to also modify trace state + // or capture additional attributes + SamplingResult result = fallback.shouldSample(parentContext, traceId, name, spanKind, + attributes, parentLinks); + + return new TransactionSamplingResult(existingKeyTransactions, startKeyTransactions, + endKeyTransactions, result); + } + + @Override + public String getDescription() { + return String.format("TransactionSampler{root:%s}", fallback.getDescription()); + } + + @Override + public String toString() { + return getDescription(); + } + + private static class TransactionSamplingResult implements SamplingResult { + + private final Collection existingKeyTransactions; + private final Collection startKeyTransactions; + private final Collection endKeyTransactions; + private final SamplingResult delegate; + + private TransactionSamplingResult(Collection existingKeyTransactions, + Collection startKeyTransactions, Collection endKeyTransactions, + SamplingResult delegate) { + + this.existingKeyTransactions = existingKeyTransactions; + this.startKeyTransactions = startKeyTransactions; + this.endKeyTransactions = endKeyTransactions; + this.delegate = delegate; + } + + @Override + public SamplingDecision getDecision() { + // always capture 100% of key transaction spans + return SamplingDecision.RECORD_AND_SAMPLE; + } + + @Override + public Attributes getAttributes() { + AttributesBuilder builder = delegate.getAttributes().toBuilder(); + + for (String startKeyTransaction : startKeyTransactions) { + builder.put("key_transaction." + startKeyTransaction, true); + builder.put("key_transaction.started." + startKeyTransaction, true); + } + + for (String existingKeyTransaction : existingKeyTransactions) { + builder.put("key_transaction." + existingKeyTransaction, true); + } + + for (String existingKeyTransaction : endKeyTransactions) { + builder.put("key_transaction.ended." + existingKeyTransaction, true); + } + + return builder.build(); + } + + @Override + public TraceState getUpdatedTraceState(TraceState parentTraceState) { + // TODO can we remove ended key transactions from trace state? + // maybe not, since the "end" span could itself still have downstream synchronous flows + // that need to be stamped and will complete before the "end" span itself completes + + TraceState updatedTraceState = delegate.getUpdatedTraceState(parentTraceState); + + if (startKeyTransactions.isEmpty()) { + return updatedTraceState; + } + + // may not match span start time exactly + long startTime = System.currentTimeMillis(); + + String newValue = startKeyTransactions.stream() + .map(name -> name + ":" + startTime) + .collect(Collectors.joining(";")); + + String existingValue = updatedTraceState.get(KeyTransactionTraceState.TRACE_STATE_KEY); + + if (existingValue != null && !existingValue.isEmpty()) { + newValue = existingValue + ";" + newValue; + } + + return updatedTraceState.toBuilder() + .put(KeyTransactionTraceState.TRACE_STATE_KEY, newValue) + .build(); + } + } +} diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSpanProcessor.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSpanProcessor.java new file mode 100644 index 00000000000..ff88500a76d --- /dev/null +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSpanProcessor.java @@ -0,0 +1,116 @@ +package com.microsoft.applicationinsights.agent.internal.keytransaction; + +import static io.opentelemetry.api.common.AttributeType.BOOLEAN; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.SpanProcessor; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +public class KeyTransactionSpanProcessor implements SpanProcessor { + + // TODO remove global state + public static volatile DoubleHistogram keyTransactionHistogram; + + public static void initMeterProvider(MeterProvider meterProvider) { + keyTransactionHistogram = meterProvider.get("keytransactions") + .histogramBuilder("key_transaction.duration") + .setDescription("Key transaction duration") + .setUnit("s") + .build(); + } + + @Override + @SuppressWarnings("unchecked") + public void onStart(Context context, ReadWriteSpan readWriteSpan) { + // copy key_transaction. attributes down to child spans + Span parentSpan = Span.fromContext(context); + if (parentSpan instanceof ReadableSpan) { + ((ReadableSpan) parentSpan).getAttributes().forEach((key, value) -> { + if (key.getKey().startsWith("key_transaction.") + && !key.getKey().startsWith("key_transaction.started.") + && !key.getKey().startsWith("key_transaction.ended.") + && key.getType() == BOOLEAN + && value instanceof Boolean) { + readWriteSpan.setAttribute((AttributeKey) key, (Boolean) value); + } + }); + } + } + + @Override + public boolean isStartRequired() { + return true; + } + + @Override + public void onEnd(ReadableSpan readableSpan) { + + if (keyTransactionHistogram == null) { + return; + } + + SpanContext parentSpanContext = readableSpan.getParentSpanContext(); + if (parentSpanContext.isValid() && !parentSpanContext.isRemote()) { + // only generating metrics for local root spans + return; + } + + List keyTransactionNames = new ArrayList<>(); + List startedKeyTransactions = new ArrayList<>(); + List endedKeyTransactions = new ArrayList<>(); + readableSpan.getAttributes().forEach((attributeKey, value) -> { + String key = attributeKey.getKey(); + if (key.startsWith("key_transaction.started.")) { + startedKeyTransactions.add(key.substring("key_transaction.started.".length())); + } else if (key.startsWith("key_transaction.ended.")) { + endedKeyTransactions.add(key.substring("key_transaction.ended.".length())); + } else if (key.startsWith("key_transaction.")) { + keyTransactionNames.add(key.substring("key_transaction.".length())); + } + }); + + if (keyTransactionNames.isEmpty()) { + return; + } + + Map keyTransactionStartTimes = KeyTransactionTraceState.getKeyTransactionStartTimes( + readableSpan.getSpanContext().getTraceState()); + + long endTime = System.currentTimeMillis(); + + for (String keyTransactionName : keyTransactionNames) { + long startTime = keyTransactionStartTimes.get(keyTransactionName); + + double duration = (endTime - startTime) / 1000.0; + + AttributesBuilder attributes = Attributes.builder() + .put("key_transaction", keyTransactionName); + + if (startedKeyTransactions.contains(keyTransactionName)) { + attributes.put("key_transaction.started", true); + } + if (endedKeyTransactions.contains(keyTransactionName)) { + attributes.put("key_transaction.ended", true); + } + + keyTransactionHistogram.record(duration, attributes.build()); + } + } + + @Override + public boolean isEndRequired() { + return true; + } +} diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTelemetryPipelineListener.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTelemetryPipelineListener.java new file mode 100644 index 00000000000..c068161e1d5 --- /dev/null +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTelemetryPipelineListener.java @@ -0,0 +1,91 @@ +package com.microsoft.applicationinsights.agent.internal.keytransaction; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.toList; + +import com.azure.json.JsonProviders; +import com.azure.json.JsonReader; +import com.azure.monitor.opentelemetry.exporter.implementation.localstorage.LocalStorageTelemetryPipelineListener; +import com.azure.monitor.opentelemetry.exporter.implementation.logging.OperationLogger; +import com.azure.monitor.opentelemetry.exporter.implementation.pipeline.TelemetryItemExporter; +import com.azure.monitor.opentelemetry.exporter.implementation.pipeline.TelemetryPipelineListener; +import com.azure.monitor.opentelemetry.exporter.implementation.pipeline.TelemetryPipelineRequest; +import com.azure.monitor.opentelemetry.exporter.implementation.pipeline.TelemetryPipelineResponse; +import io.opentelemetry.sdk.common.CompletableResultCode; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class KeyTransactionTelemetryPipelineListener implements TelemetryPipelineListener { + + private static final Logger logger = LoggerFactory.getLogger( + KeyTransactionTelemetryPipelineListener.class); + + private static final OperationLogger operationLogger = new OperationLogger( + TelemetryItemExporter.class, "Parsing response from ingestion service"); + + @Override + public void onResponse(TelemetryPipelineRequest request, TelemetryPipelineResponse response) { + + if (logger.isDebugEnabled()) { + logger.debug("request: {}", requestToString(request)); + logger.debug("response: {}", response.getBody()); + } + + NewResponse parsedResponse; + try (JsonReader reader = JsonProviders.createReader(response.getBody())) { + parsedResponse = NewResponse.fromJson(reader); + operationLogger.recordSuccess(); + } catch (IOException e) { + operationLogger.recordFailure(e.getMessage(), e); + return; + } + + if (parsedResponse.getSdkConfigurations() != null) { + KeyTransactionConfigSupplier.getInstance().set( + parsedResponse.getSdkConfigurations() + .stream() + .map(SdkConfiguration::getValue) + .collect(toList()) + ); + } + } + + @Override + public void onException(TelemetryPipelineRequest telemetryPipelineRequest, String s, + Throwable throwable) { + // ignore + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + + private static String requestToString(TelemetryPipelineRequest request) { + List originalByteBuffers = request.getByteBuffers(); + byte[] gzippedBytes = convertByteBufferListToByteArray(originalByteBuffers); + byte[] ungzippedBytes = LocalStorageTelemetryPipelineListener.ungzip(gzippedBytes); + return new String(ungzippedBytes, UTF_8); + } + + // convert a list of byte buffers to a big byte array + private static byte[] convertByteBufferListToByteArray(List byteBuffers) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (ByteBuffer buffer : byteBuffers) { + byte[] arr = new byte[buffer.remaining()]; + buffer.get(arr); + try { + baos.write(arr); + } catch (IOException e) { + // this should never happen since ByteArrayOutputStream doesn't throw IOException + throw new IllegalStateException(e); + } + } + + return baos.toByteArray(); + } +} diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceState.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceState.java new file mode 100644 index 00000000000..7524ed7c7e0 --- /dev/null +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceState.java @@ -0,0 +1,75 @@ +package com.microsoft.applicationinsights.agent.internal.keytransaction; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; + +import io.opentelemetry.api.trace.TraceState; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +class KeyTransactionTraceState { + + // NOTE: dots are not valid in trace state keys, so we use underscores + // example: "microsoft_kt=mykeytransaction:starttimemillis;mykeytransaction2:starttimemillis" + static final String TRACE_STATE_KEY = "microsoft_kt"; + + static Set getKeyTransactionNames(TraceState traceState) { + return getKeyTransactionNames(traceState.get(TRACE_STATE_KEY)); + } + + // visible for testing + @SuppressWarnings("MixedMutabilityReturnType") + static Set getKeyTransactionNames(String value) { + if (value == null) { + return emptySet(); + } + + Set names = new HashSet<>(); + for (String part : value.split(";")) { + int index = part.lastIndexOf(':'); + if (index == -1) { + // invalid format, ignore + continue; + } + names.add(part.substring(0, index)); + } + + return names; + } + + static Map getKeyTransactionStartTimes(TraceState traceState) { + return getKeyTransactionStartTimes(traceState.get(TRACE_STATE_KEY)); + } + + // visible for testing + @SuppressWarnings("MixedMutabilityReturnType") + static Map getKeyTransactionStartTimes(String value) { + if (value == null) { + return emptyMap(); + } + + Map names = new HashMap<>(); + for (String part : value.split(";")) { + int index = part.lastIndexOf(':'); + if (index == -1) { + // invalid format, ignore + continue; + } + String key = part.substring(0, index); + long val; + try { + val = Long.parseLong(part.substring(index + 1)); + } catch (NumberFormatException e) { + // invalid format, ignore + continue; + } + names.put(key, val); + } + + return names; + } + + private KeyTransactionTraceState() {} +} diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/NewResponse.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/NewResponse.java new file mode 100644 index 00000000000..5890a0bbbcf --- /dev/null +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/NewResponse.java @@ -0,0 +1,33 @@ +package com.microsoft.applicationinsights.agent.internal.keytransaction; + +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import java.io.IOException; +import java.util.List; + +class NewResponse { + + private List sdkConfigurations; + + public List getSdkConfigurations() { + return sdkConfigurations; + } + + static NewResponse fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject((reader) -> { + NewResponse deserializedValue = new NewResponse(); + + while(reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + if ("sdkConfiguration".equals(fieldName)) { + deserializedValue.sdkConfigurations = reader.readArray(SdkConfiguration::fromJson); + } else { + reader.skipChildren(); + } + } + + return deserializedValue; + }); + } +} diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/SdkConfiguration.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/SdkConfiguration.java new file mode 100644 index 00000000000..951732c4d54 --- /dev/null +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/SdkConfiguration.java @@ -0,0 +1,39 @@ +package com.microsoft.applicationinsights.agent.internal.keytransaction; + +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import java.io.IOException; + +public class SdkConfiguration { + + private String key; + private KeyTransactionConfig value; // for hackathon only supporting one type of value + + public String getKey() { + return key; + } + + public KeyTransactionConfig getValue() { + return value; + } + + static SdkConfiguration fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject((reader) -> { + SdkConfiguration deserializedValue = new SdkConfiguration(); + + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + if ("Key".equals(fieldName)) { + deserializedValue.key = reader.getString(); + } else if ("Value".equals(fieldName)) { + deserializedValue.value = KeyTransactionConfig.fromJson(reader); + } else { + reader.skipChildren(); + } + } + + return deserializedValue; + }); + } +} diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/Samplers.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/Samplers.java index 8f6dfaecd42..b34b024ca05 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/Samplers.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/Samplers.java @@ -5,6 +5,8 @@ import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; import com.microsoft.applicationinsights.agent.internal.configuration.Configuration.SamplingOverride; +import com.microsoft.applicationinsights.agent.internal.keytransaction.KeyTransactionSampler; +import com.microsoft.applicationinsights.agent.internal.keytransaction.KeyTransactionConfigSupplier; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.util.List; import java.util.stream.Collectors; @@ -46,16 +48,20 @@ public static Sampler getSampler( new AiOverrideSampler(requestSamplingOverrides, dependencySamplingOverrides, sampler); } - if (!samplingPreview.parentBased) { - return sampler; + if (samplingPreview.parentBased) { + // when using parent-based sampling, sampling overrides still take precedence + + // IMPORTANT, the parent-based sampler is useful for interop with other sampling mechanisms, as + // it will ensure consistent traces, however it does not accurately compute item counts, since + // item counts are not propagated in trace state (yet) + return Sampler.parentBasedBuilder(sampler).build(); } - // when using parent-based sampling, sampling overrides still take precedence + if (KeyTransactionConfigSupplier.KEY_TRANSACTIONS_ENABLED) { + sampler = KeyTransactionSampler.create(KeyTransactionConfigSupplier.getInstance(), sampler); + } - // IMPORTANT, the parent-based sampler is useful for interop with other sampling mechanisms, as - // it will ensure consistent traces, however it does not accurately compute item counts, since - // item counts are not propagated in trace state (yet) - return Sampler.parentBasedBuilder(sampler).build(); + return sampler; } private Samplers() {} diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/telemetry/TelemetryClient.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/telemetry/TelemetryClient.java index f719e8ee12f..20c1b991404 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/telemetry/TelemetryClient.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/telemetry/TelemetryClient.java @@ -34,6 +34,8 @@ import com.azure.monitor.opentelemetry.exporter.implementation.utils.TempDirs; import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; import com.microsoft.applicationinsights.agent.internal.httpclient.LazyHttpClient; +import com.microsoft.applicationinsights.agent.internal.keytransaction.KeyTransactionConfigSupplier; +import com.microsoft.applicationinsights.agent.internal.keytransaction.KeyTransactionTelemetryPipelineListener; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.resources.Resource; import java.io.File; @@ -254,6 +256,11 @@ private BatchItemProcessor initBatchItemProcessor( false)); } + if (KeyTransactionConfigSupplier.KEY_TRANSACTIONS_ENABLED) { + telemetryPipelineListener = TelemetryPipelineListener.composite(telemetryPipelineListener, + new KeyTransactionTelemetryPipelineListener()); + } + return BatchItemProcessor.builder( new TelemetryItemExporter(telemetryPipeline, telemetryPipelineListener)) .setMaxQueueSize(exportQueueCapacity) diff --git a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java new file mode 100644 index 00000000000..fb1aa4907f7 --- /dev/null +++ b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java @@ -0,0 +1,54 @@ +package com.microsoft.applicationinsights.agent.internal.keytransaction; + +import static com.microsoft.applicationinsights.agent.internal.keytransaction.KeyTransactionConfig.Operator.EQUALS; +import static org.assertj.core.api.Assertions.assertThat; + +import com.azure.json.JsonProviders; +import java.io.InputStream; +import org.junit.jupiter.api.Test; + +class KeyTransactionConfigTest { + + @Test + void test() throws Exception { + InputStream in = KeyTransactionConfigTest.class.getResourceAsStream("demo.json"); + NewResponse parsedResponse = NewResponse.fromJson(JsonProviders.createReader(in)); + + assertThat(parsedResponse.getSdkConfigurations()).hasSize(2); + + SdkConfiguration earthOrbitSdkConfiguration = parsedResponse.getSdkConfigurations().get(0); + assertThat(earthOrbitSdkConfiguration.getKey()).isEqualTo("Transaction"); + + KeyTransactionConfig earthOrbitKeyTransactionConfig = earthOrbitSdkConfiguration.getValue(); + assertThat(earthOrbitKeyTransactionConfig.getName()).isEqualTo("EarthOrbit"); + + assertThat(earthOrbitKeyTransactionConfig.getStartCriteria()).hasSize(1); + KeyTransactionConfig.Criterion earthOrbitStartCriteria = + earthOrbitKeyTransactionConfig.getStartCriteria().get(0); + assertThat(earthOrbitStartCriteria.getField().getKey()).isEqualTo("http.path"); + assertThat(earthOrbitStartCriteria.getOperator()).isEqualTo(EQUALS); + assertThat(earthOrbitStartCriteria.getValue()).isEqualTo("earth"); + + assertThat(earthOrbitKeyTransactionConfig.getEndCriteria()).isEmpty(); + + SdkConfiguration marsMissionSdkConfiguration = parsedResponse.getSdkConfigurations().get(1); + assertThat(marsMissionSdkConfiguration.getKey()).isEqualTo("Transaction"); + + KeyTransactionConfig marsMissionKeyTransactionConfig = marsMissionSdkConfiguration.getValue(); + assertThat(marsMissionKeyTransactionConfig.getName()).isEqualTo("MarsMission"); + + assertThat(marsMissionKeyTransactionConfig.getStartCriteria()).hasSize(1); + KeyTransactionConfig.Criterion marsMissionStartCriteria = + marsMissionKeyTransactionConfig.getStartCriteria().get(0); + assertThat(marsMissionStartCriteria.getField().getKey()).isEqualTo("http.path"); + assertThat(marsMissionStartCriteria.getOperator()).isEqualTo(EQUALS); + assertThat(marsMissionStartCriteria.getValue()).isEqualTo("mars"); + + assertThat(marsMissionKeyTransactionConfig.getEndCriteria()).hasSize(1); + KeyTransactionConfig.Criterion marsMissionEndCriteria = + marsMissionKeyTransactionConfig.getEndCriteria().get(0); + assertThat(marsMissionEndCriteria.getField().getKey()).isEqualTo("messaging.todo"); + assertThat(marsMissionEndCriteria.getOperator()).isEqualTo(EQUALS); + assertThat(marsMissionEndCriteria.getValue()).isEqualTo("todo"); + } +} diff --git a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceStateTest.java b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceStateTest.java new file mode 100644 index 00000000000..b0417108ee2 --- /dev/null +++ b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceStateTest.java @@ -0,0 +1,37 @@ +package com.microsoft.applicationinsights.agent.internal.keytransaction; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static com.microsoft.applicationinsights.agent.internal.keytransaction.KeyTransactionTraceState.getKeyTransactionStartTimes; +import static org.assertj.core.api.Assertions.assertThat; + +class KeyTransactionTraceStateTest { + + @Test + void testNull() { + assertThat(getKeyTransactionStartTimes((String) null)).isEmpty(); + } + + @Test + void testEmpty() { + assertThat(getKeyTransactionStartTimes("")).isEmpty(); + } + + @Test + void testSingle() { + Map startTimes = getKeyTransactionStartTimes("abc:123"); + assertThat(startTimes).containsOnlyKeys("abc"); + assertThat(startTimes.get("abc")).isEqualTo(123L); + } + + @Test + void testMultiple() { + Map startTimes = getKeyTransactionStartTimes("abc:123;qrs:456;xyz:789"); + assertThat(startTimes).containsOnlyKeys("abc", "qrs", "xyz"); + assertThat(startTimes.get("abc")).isEqualTo(123L); + assertThat(startTimes.get("qrs")).isEqualTo(456L); + assertThat(startTimes.get("xyz")).isEqualTo(789L); + } +} diff --git a/agent/agent-tooling/src/test/resources/com/microsoft/applicationinsights/agent/internal/keytransaction/demo.json b/agent/agent-tooling/src/test/resources/com/microsoft/applicationinsights/agent/internal/keytransaction/demo.json new file mode 100644 index 00000000000..54eae2f3427 --- /dev/null +++ b/agent/agent-tooling/src/test/resources/com/microsoft/applicationinsights/agent/internal/keytransaction/demo.json @@ -0,0 +1,42 @@ +{ + "itemsReceived": 13, + "itemsAccepted": 13, + "appId": null, + "errors": [], + "sdkConfiguration": [ + { + "Key": "Transaction", + "Value": { + "Name": "EarthOrbit", + "StartCriteria": [ + { + "Field": "url.path", + "Operator": "==", + "Value": "/earth" + } + ], + "EndCriteria": [] + } + }, + { + "Key": "Transaction", + "Value": { + "Name": "MarsMission", + "StartCriteria": [ + { + "Field": "url.path", + "Operator": "==", + "Value": "/mars" + } + ], + "EndCriteria": [ + { + "Field": "messaging.todo", + "Operator": "==", + "Value": "todo" + } + ] + } + } + ] +} From fad55ad41ded3ade01765115fa8f37010deaa877 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 20 Sep 2024 18:21:04 -0700 Subject: [PATCH 2/6] Async flow working --- .../KeyTransactionConfigSupplier.java | 103 ++++++++++-------- ...yTransactionTelemetryPipelineListener.java | 2 +- .../KeyTransactionConfigTest.java | 17 ++- .../agent/internal/keytransaction/demo.json | 9 +- 4 files changed, 75 insertions(+), 56 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java index 5379d1f18aa..4847b2eff9a 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java @@ -1,22 +1,25 @@ package com.microsoft.applicationinsights.agent.internal.keytransaction; +import static java.util.stream.Collectors.toList; + import com.azure.json.JsonProviders; import java.io.IOException; import java.util.List; import java.util.function.Supplier; -import static java.util.stream.Collectors.toList; - public class KeyTransactionConfigSupplier implements Supplier> { public static final boolean KEY_TRANSACTIONS_ENABLED = true; + public static final boolean USE_HARDCODED_CONFIG = false; // TODO remove reliance on global private static final KeyTransactionConfigSupplier instance = new KeyTransactionConfigSupplier(); -// static { -// instance.set(hardcodedDemo()); -// } + static { + if (USE_HARDCODED_CONFIG) { + instance.set(hardcodedDemo()); + } + } public static KeyTransactionConfigSupplier getInstance() { return instance; @@ -33,53 +36,57 @@ public void set(List configs) { this.configs = configs; } - @SuppressWarnings("unused") private static List hardcodedDemo() { List configs; try { configs = NewResponse.fromJson(JsonProviders.createReader( - "{\n" - + " \"itemsReceived\": 13,\n" - + " \"itemsAccepted\": 13,\n" - + " \"appId\": null,\n" - + " \"errors\": [],\n" - + " \"sdkConfiguration\": [\n" - + " {\n" - + " \"Key\": \"Transaction\",\n" - + " \"Value\": {\n" - + " \"Name\": \"EarthOrbit\",\n" - + " \"StartCriteria\": [\n" - + " {\n" - + " \"Field\": \"url.path\",\n" - + " \"Operator\": \"==\",\n" - + " \"Value\": \"/earth\"\n" - + " }\n" - + " ],\n" - + " \"EndCriteria\": []\n" - + " }\n" - + " },\n" - + " {\n" - + " \"Key\": \"Transaction\",\n" - + " \"Value\": {\n" - + " \"Name\": \"MarsMission\",\n" - + " \"StartCriteria\": [\n" - + " {\n" - + " \"Field\": \"url.path\",\n" - + " \"Operator\": \"==\",\n" - + " \"Value\": \"/mars\"\n" - + " }\n" - + " ],\n" - + " \"EndCriteria\": [\n" - + " {\n" - + " \"Field\": \"messaging.todo\",\n" - + " \"Operator\": \"==\",\n" - + " \"Value\": \"todo\"\n" - + " }\n" - + " ]\n" - + " }\n" - + " }\n" - + " ]\n" - + "}\n" + "{\n" + + " \"itemsReceived\": 13,\n" + + " \"itemsAccepted\": 13,\n" + + " \"appId\": null,\n" + + " \"errors\": [],\n" + + " \"sdkConfiguration\": [\n" + + " {\n" + + " \"Key\": \"Transaction\",\n" + + " \"Value\": {\n" + + " \"Name\": \"EarthOrbit\",\n" + + " \"StartCriteria\": [\n" + + " {\n" + + " \"Field\": \"url.path\",\n" + + " \"Operator\": \"==\",\n" + + " \"Value\": \"/earth\"\n" + + " }\n" + + " ],\n" + + " \"EndCriteria\": []\n" + + " }\n" + + " },\n" + + " {\n" + + " \"Key\": \"Transaction\",\n" + + " \"Value\": {\n" + + " \"Name\": \"MarsMission\",\n" + + " \"StartCriteria\": [\n" + + " {\n" + + " \"Field\": \"url.path\",\n" + + " \"Operator\": \"==\",\n" + + " \"Value\": \"/mars\"\n" + + " }\n" + + " ],\n" + + " \"EndCriteria\": [\n" + + " {\n" + + " \"Field\": \"messaging.operation\",\n" + + " \"Operator\": \"==\",\n" + + " \"Value\": \"process\"\n" + + " },\n" + + " {\n" + + " \"Field\": \"messaging.destination.name\",\n" + + " \"Operator\": \"==\",\n" + + " \"Value\": \"space\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + " ]\n" + + "}\n" )) .getSdkConfigurations() .stream() diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTelemetryPipelineListener.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTelemetryPipelineListener.java index c068161e1d5..8a7f0283448 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTelemetryPipelineListener.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTelemetryPipelineListener.java @@ -44,7 +44,7 @@ public void onResponse(TelemetryPipelineRequest request, TelemetryPipelineRespon return; } - if (parsedResponse.getSdkConfigurations() != null) { + if (parsedResponse.getSdkConfigurations() != null && !KeyTransactionConfigSupplier.USE_HARDCODED_CONFIG) { KeyTransactionConfigSupplier.getInstance().set( parsedResponse.getSdkConfigurations() .stream() diff --git a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java index fb1aa4907f7..e1f7ddae26b 100644 --- a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java +++ b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java @@ -44,11 +44,18 @@ void test() throws Exception { assertThat(marsMissionStartCriteria.getOperator()).isEqualTo(EQUALS); assertThat(marsMissionStartCriteria.getValue()).isEqualTo("mars"); - assertThat(marsMissionKeyTransactionConfig.getEndCriteria()).hasSize(1); - KeyTransactionConfig.Criterion marsMissionEndCriteria = + assertThat(marsMissionKeyTransactionConfig.getEndCriteria()).hasSize(2); + + KeyTransactionConfig.Criterion marsMissionEndCriteria1 = + marsMissionKeyTransactionConfig.getEndCriteria().get(0); + assertThat(marsMissionEndCriteria1.getField().getKey()).isEqualTo("messaging.operation"); + assertThat(marsMissionEndCriteria1.getOperator()).isEqualTo(EQUALS); + assertThat(marsMissionEndCriteria1.getValue()).isEqualTo("process"); + + KeyTransactionConfig.Criterion marsMissionEndCriteria2 = marsMissionKeyTransactionConfig.getEndCriteria().get(0); - assertThat(marsMissionEndCriteria.getField().getKey()).isEqualTo("messaging.todo"); - assertThat(marsMissionEndCriteria.getOperator()).isEqualTo(EQUALS); - assertThat(marsMissionEndCriteria.getValue()).isEqualTo("todo"); + assertThat(marsMissionEndCriteria2.getField().getKey()).isEqualTo("messaging.destination.name"); + assertThat(marsMissionEndCriteria2.getOperator()).isEqualTo(EQUALS); + assertThat(marsMissionEndCriteria2.getValue()).isEqualTo("space"); } } diff --git a/agent/agent-tooling/src/test/resources/com/microsoft/applicationinsights/agent/internal/keytransaction/demo.json b/agent/agent-tooling/src/test/resources/com/microsoft/applicationinsights/agent/internal/keytransaction/demo.json index 54eae2f3427..a5db272f97a 100644 --- a/agent/agent-tooling/src/test/resources/com/microsoft/applicationinsights/agent/internal/keytransaction/demo.json +++ b/agent/agent-tooling/src/test/resources/com/microsoft/applicationinsights/agent/internal/keytransaction/demo.json @@ -31,9 +31,14 @@ ], "EndCriteria": [ { - "Field": "messaging.todo", + "Field": "messaging.operation", "Operator": "==", - "Value": "todo" + "Value": "process" + }, + { + "Field": "messaging.destination.name", + "Operator": "==", + "Value": "space" } ] } From e2a6b7395eaea3aaa8f0cdbc87d65fd382a05160 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 23 Sep 2024 14:06:25 -0700 Subject: [PATCH 3/6] spotless --- .../keytransaction/KeyTransactionConfig.java | 83 +++++++------ .../KeyTransactionConfigSupplier.java | 110 +++++++++--------- .../keytransaction/KeyTransactionSampler.java | 37 +++--- .../KeyTransactionSpanProcessor.java | 69 ++++++----- ...yTransactionTelemetryPipelineListener.java | 29 ++--- .../KeyTransactionTraceState.java | 3 + .../internal/keytransaction/NewResponse.java | 34 +++--- .../keytransaction/SdkConfiguration.java | 38 +++--- .../agent/internal/sampling/Samplers.java | 5 +- .../internal/telemetry/TelemetryClient.java | 5 +- .../KeyTransactionConfigTest.java | 3 + .../KeyTransactionTraceStateTest.java | 10 +- 12 files changed, 235 insertions(+), 191 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfig.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfig.java index 4023349782c..64d963f33d5 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfig.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfig.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.applicationinsights.agent.internal.keytransaction; import com.azure.json.JsonReader; @@ -26,25 +29,26 @@ List getEndCriteria() { } public static KeyTransactionConfig fromJson(JsonReader jsonReader) throws IOException { - return jsonReader.readObject((reader) -> { - KeyTransactionConfig deserializedValue = new KeyTransactionConfig(); - - while (reader.nextToken() != JsonToken.END_OBJECT) { - String fieldName = reader.getFieldName(); - reader.nextToken(); - if ("Name".equals(fieldName)) { - deserializedValue.name = reader.getString(); - } else if ("StartCriteria".equals(fieldName)) { - deserializedValue.startCriteria = reader.readArray(Criterion::fromJson); - } else if ("EndCriteria".equals(fieldName)) { - deserializedValue.endCriteria = reader.readArray(Criterion::fromJson); - } else { - reader.skipChildren(); - } - } + return jsonReader.readObject( + (reader) -> { + KeyTransactionConfig deserializedValue = new KeyTransactionConfig(); + + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + if ("Name".equals(fieldName)) { + deserializedValue.name = reader.getString(); + } else if ("StartCriteria".equals(fieldName)) { + deserializedValue.startCriteria = reader.readArray(Criterion::fromJson); + } else if ("EndCriteria".equals(fieldName)) { + deserializedValue.endCriteria = reader.readArray(Criterion::fromJson); + } else { + reader.skipChildren(); + } + } - return deserializedValue; - }); + return deserializedValue; + }); } public static boolean matches(Attributes attributes, List criteria) { @@ -98,30 +102,33 @@ Operator getOperator() { } public static Criterion fromJson(JsonReader jsonReader) throws IOException { - return jsonReader.readObject((reader) -> { - Criterion deserializedValue = new Criterion(); - - while (reader.nextToken() != JsonToken.END_OBJECT) { - String fieldName = reader.getFieldName(); - reader.nextToken(); - if ("Field".equals(fieldName)) { - deserializedValue.field = AttributeKey.stringKey(reader.getString()); - } else if ("Operator".equals(fieldName)) { - deserializedValue.operator = Operator.from(reader.getString()); - } else if ("Value".equals(fieldName)) { - deserializedValue.value = reader.getString(); - } else { - reader.skipChildren(); - } - } - - return deserializedValue; - }); + return jsonReader.readObject( + (reader) -> { + Criterion deserializedValue = new Criterion(); + + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + if ("Field".equals(fieldName)) { + deserializedValue.field = AttributeKey.stringKey(reader.getString()); + } else if ("Operator".equals(fieldName)) { + deserializedValue.operator = Operator.from(reader.getString()); + } else if ("Value".equals(fieldName)) { + deserializedValue.value = reader.getString(); + } else { + reader.skipChildren(); + } + } + + return deserializedValue; + }); } } public enum Operator { - EQUALS, STARTSWITH, CONTAINS; + EQUALS, + STARTSWITH, + CONTAINS; private static Operator from(String value) { switch (value) { diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java index 4847b2eff9a..b8e28b55ecb 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.applicationinsights.agent.internal.keytransaction; import static java.util.stream.Collectors.toList; @@ -39,59 +42,60 @@ public void set(List configs) { private static List hardcodedDemo() { List configs; try { - configs = NewResponse.fromJson(JsonProviders.createReader( - "{\n" - + " \"itemsReceived\": 13,\n" - + " \"itemsAccepted\": 13,\n" - + " \"appId\": null,\n" - + " \"errors\": [],\n" - + " \"sdkConfiguration\": [\n" - + " {\n" - + " \"Key\": \"Transaction\",\n" - + " \"Value\": {\n" - + " \"Name\": \"EarthOrbit\",\n" - + " \"StartCriteria\": [\n" - + " {\n" - + " \"Field\": \"url.path\",\n" - + " \"Operator\": \"==\",\n" - + " \"Value\": \"/earth\"\n" - + " }\n" - + " ],\n" - + " \"EndCriteria\": []\n" - + " }\n" - + " },\n" - + " {\n" - + " \"Key\": \"Transaction\",\n" - + " \"Value\": {\n" - + " \"Name\": \"MarsMission\",\n" - + " \"StartCriteria\": [\n" - + " {\n" - + " \"Field\": \"url.path\",\n" - + " \"Operator\": \"==\",\n" - + " \"Value\": \"/mars\"\n" - + " }\n" - + " ],\n" - + " \"EndCriteria\": [\n" - + " {\n" - + " \"Field\": \"messaging.operation\",\n" - + " \"Operator\": \"==\",\n" - + " \"Value\": \"process\"\n" - + " },\n" - + " {\n" - + " \"Field\": \"messaging.destination.name\",\n" - + " \"Operator\": \"==\",\n" - + " \"Value\": \"space\"\n" - + " }\n" - + " ]\n" - + " }\n" - + " }\n" - + " ]\n" - + "}\n" - )) - .getSdkConfigurations() - .stream() - .map(SdkConfiguration::getValue) - .collect(toList()); + configs = + NewResponse.fromJson( + JsonProviders.createReader( + "{\n" + + " \"itemsReceived\": 13,\n" + + " \"itemsAccepted\": 13,\n" + + " \"appId\": null,\n" + + " \"errors\": [],\n" + + " \"sdkConfiguration\": [\n" + + " {\n" + + " \"Key\": \"Transaction\",\n" + + " \"Value\": {\n" + + " \"Name\": \"EarthOrbit\",\n" + + " \"StartCriteria\": [\n" + + " {\n" + + " \"Field\": \"url.path\",\n" + + " \"Operator\": \"==\",\n" + + " \"Value\": \"/earth\"\n" + + " }\n" + + " ],\n" + + " \"EndCriteria\": []\n" + + " }\n" + + " },\n" + + " {\n" + + " \"Key\": \"Transaction\",\n" + + " \"Value\": {\n" + + " \"Name\": \"MarsMission\",\n" + + " \"StartCriteria\": [\n" + + " {\n" + + " \"Field\": \"url.path\",\n" + + " \"Operator\": \"==\",\n" + + " \"Value\": \"/mars\"\n" + + " }\n" + + " ],\n" + + " \"EndCriteria\": [\n" + + " {\n" + + " \"Field\": \"messaging.operation\",\n" + + " \"Operator\": \"==\",\n" + + " \"Value\": \"process\"\n" + + " },\n" + + " {\n" + + " \"Field\": \"messaging.destination.name\",\n" + + " \"Operator\": \"==\",\n" + + " \"Value\": \"space\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + " ]\n" + + "}\n")) + .getSdkConfigurations() + .stream() + .map(SdkConfiguration::getValue) + .collect(toList()); } catch (IOException e) { throw new IllegalStateException(e); } diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSampler.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSampler.java index 2c07081a717..f504af0432f 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSampler.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSampler.java @@ -1,7 +1,5 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. package com.microsoft.applicationinsights.agent.internal.keytransaction; @@ -33,8 +31,8 @@ private KeyTransactionSampler(Supplier> configs, Samp this.fallback = fallback; } - public static KeyTransactionSampler create(Supplier> configs, - Sampler fallback) { + public static KeyTransactionSampler create( + Supplier> configs, Sampler fallback) { return new KeyTransactionSampler(configs, fallback); } @@ -56,8 +54,7 @@ public SamplingResult shouldSample( List configs = this.configs.get(); Set existingKeyTransactions = - KeyTransactionTraceState.getKeyTransactionStartTimes(spanContext.getTraceState()) - .keySet(); + KeyTransactionTraceState.getKeyTransactionStartTimes(spanContext.getTraceState()).keySet(); List startKeyTransactions = new ArrayList<>(); List endKeyTransactions = new ArrayList<>(); @@ -65,7 +62,8 @@ public SamplingResult shouldSample( if (existingKeyTransactions.contains(config.getName())) { // consider ending it - if (!config.getEndCriteria().isEmpty() && KeyTransactionConfig.matches(attributes, config.getEndCriteria())) { + if (!config.getEndCriteria().isEmpty() + && KeyTransactionConfig.matches(attributes, config.getEndCriteria())) { endKeyTransactions.add(config.getName()); } } else { @@ -83,11 +81,11 @@ public SamplingResult shouldSample( // always delegate to fallback sampler to give it a chance to also modify trace state // or capture additional attributes - SamplingResult result = fallback.shouldSample(parentContext, traceId, name, spanKind, - attributes, parentLinks); + SamplingResult result = + fallback.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks); - return new TransactionSamplingResult(existingKeyTransactions, startKeyTransactions, - endKeyTransactions, result); + return new TransactionSamplingResult( + existingKeyTransactions, startKeyTransactions, endKeyTransactions, result); } @Override @@ -107,8 +105,10 @@ private static class TransactionSamplingResult implements SamplingResult { private final Collection endKeyTransactions; private final SamplingResult delegate; - private TransactionSamplingResult(Collection existingKeyTransactions, - Collection startKeyTransactions, Collection endKeyTransactions, + private TransactionSamplingResult( + Collection existingKeyTransactions, + Collection startKeyTransactions, + Collection endKeyTransactions, SamplingResult delegate) { this.existingKeyTransactions = existingKeyTransactions; @@ -158,9 +158,10 @@ public TraceState getUpdatedTraceState(TraceState parentTraceState) { // may not match span start time exactly long startTime = System.currentTimeMillis(); - String newValue = startKeyTransactions.stream() - .map(name -> name + ":" + startTime) - .collect(Collectors.joining(";")); + String newValue = + startKeyTransactions.stream() + .map(name -> name + ":" + startTime) + .collect(Collectors.joining(";")); String existingValue = updatedTraceState.get(KeyTransactionTraceState.TRACE_STATE_KEY); diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSpanProcessor.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSpanProcessor.java index ff88500a76d..83e858d0307 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSpanProcessor.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSpanProcessor.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.applicationinsights.agent.internal.keytransaction; import static io.opentelemetry.api.common.AttributeType.BOOLEAN; @@ -17,18 +20,19 @@ import java.util.List; import java.util.Map; - public class KeyTransactionSpanProcessor implements SpanProcessor { // TODO remove global state public static volatile DoubleHistogram keyTransactionHistogram; public static void initMeterProvider(MeterProvider meterProvider) { - keyTransactionHistogram = meterProvider.get("keytransactions") - .histogramBuilder("key_transaction.duration") - .setDescription("Key transaction duration") - .setUnit("s") - .build(); + keyTransactionHistogram = + meterProvider + .get("keytransactions") + .histogramBuilder("key_transaction.duration") + .setDescription("Key transaction duration") + .setUnit("s") + .build(); } @Override @@ -37,15 +41,18 @@ public void onStart(Context context, ReadWriteSpan readWriteSpan) { // copy key_transaction. attributes down to child spans Span parentSpan = Span.fromContext(context); if (parentSpan instanceof ReadableSpan) { - ((ReadableSpan) parentSpan).getAttributes().forEach((key, value) -> { - if (key.getKey().startsWith("key_transaction.") - && !key.getKey().startsWith("key_transaction.started.") - && !key.getKey().startsWith("key_transaction.ended.") - && key.getType() == BOOLEAN - && value instanceof Boolean) { - readWriteSpan.setAttribute((AttributeKey) key, (Boolean) value); - } - }); + ((ReadableSpan) parentSpan) + .getAttributes() + .forEach( + (key, value) -> { + if (key.getKey().startsWith("key_transaction.") + && !key.getKey().startsWith("key_transaction.started.") + && !key.getKey().startsWith("key_transaction.ended.") + && key.getType() == BOOLEAN + && value instanceof Boolean) { + readWriteSpan.setAttribute((AttributeKey) key, (Boolean) value); + } + }); } } @@ -70,23 +77,27 @@ public void onEnd(ReadableSpan readableSpan) { List keyTransactionNames = new ArrayList<>(); List startedKeyTransactions = new ArrayList<>(); List endedKeyTransactions = new ArrayList<>(); - readableSpan.getAttributes().forEach((attributeKey, value) -> { - String key = attributeKey.getKey(); - if (key.startsWith("key_transaction.started.")) { - startedKeyTransactions.add(key.substring("key_transaction.started.".length())); - } else if (key.startsWith("key_transaction.ended.")) { - endedKeyTransactions.add(key.substring("key_transaction.ended.".length())); - } else if (key.startsWith("key_transaction.")) { - keyTransactionNames.add(key.substring("key_transaction.".length())); - } - }); + readableSpan + .getAttributes() + .forEach( + (attributeKey, value) -> { + String key = attributeKey.getKey(); + if (key.startsWith("key_transaction.started.")) { + startedKeyTransactions.add(key.substring("key_transaction.started.".length())); + } else if (key.startsWith("key_transaction.ended.")) { + endedKeyTransactions.add(key.substring("key_transaction.ended.".length())); + } else if (key.startsWith("key_transaction.")) { + keyTransactionNames.add(key.substring("key_transaction.".length())); + } + }); if (keyTransactionNames.isEmpty()) { return; } - Map keyTransactionStartTimes = KeyTransactionTraceState.getKeyTransactionStartTimes( - readableSpan.getSpanContext().getTraceState()); + Map keyTransactionStartTimes = + KeyTransactionTraceState.getKeyTransactionStartTimes( + readableSpan.getSpanContext().getTraceState()); long endTime = System.currentTimeMillis(); @@ -95,8 +106,8 @@ public void onEnd(ReadableSpan readableSpan) { double duration = (endTime - startTime) / 1000.0; - AttributesBuilder attributes = Attributes.builder() - .put("key_transaction", keyTransactionName); + AttributesBuilder attributes = + Attributes.builder().put("key_transaction", keyTransactionName); if (startedKeyTransactions.contains(keyTransactionName)) { attributes.put("key_transaction.started", true); diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTelemetryPipelineListener.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTelemetryPipelineListener.java index 8a7f0283448..001d07faf7e 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTelemetryPipelineListener.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTelemetryPipelineListener.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.applicationinsights.agent.internal.keytransaction; import static java.nio.charset.StandardCharsets.UTF_8; @@ -21,11 +24,11 @@ public class KeyTransactionTelemetryPipelineListener implements TelemetryPipelineListener { - private static final Logger logger = LoggerFactory.getLogger( - KeyTransactionTelemetryPipelineListener.class); + private static final Logger logger = + LoggerFactory.getLogger(KeyTransactionTelemetryPipelineListener.class); - private static final OperationLogger operationLogger = new OperationLogger( - TelemetryItemExporter.class, "Parsing response from ingestion service"); + private static final OperationLogger operationLogger = + new OperationLogger(TelemetryItemExporter.class, "Parsing response from ingestion service"); @Override public void onResponse(TelemetryPipelineRequest request, TelemetryPipelineResponse response) { @@ -44,19 +47,19 @@ public void onResponse(TelemetryPipelineRequest request, TelemetryPipelineRespon return; } - if (parsedResponse.getSdkConfigurations() != null && !KeyTransactionConfigSupplier.USE_HARDCODED_CONFIG) { - KeyTransactionConfigSupplier.getInstance().set( - parsedResponse.getSdkConfigurations() - .stream() - .map(SdkConfiguration::getValue) - .collect(toList()) - ); + if (parsedResponse.getSdkConfigurations() != null + && !KeyTransactionConfigSupplier.USE_HARDCODED_CONFIG) { + KeyTransactionConfigSupplier.getInstance() + .set( + parsedResponse.getSdkConfigurations().stream() + .map(SdkConfiguration::getValue) + .collect(toList())); } } @Override - public void onException(TelemetryPipelineRequest telemetryPipelineRequest, String s, - Throwable throwable) { + public void onException( + TelemetryPipelineRequest telemetryPipelineRequest, String s, Throwable throwable) { // ignore } diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceState.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceState.java index 7524ed7c7e0..8204032680d 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceState.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceState.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.applicationinsights.agent.internal.keytransaction; import static java.util.Collections.emptyMap; diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/NewResponse.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/NewResponse.java index 5890a0bbbcf..1512aeda96c 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/NewResponse.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/NewResponse.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.applicationinsights.agent.internal.keytransaction; import com.azure.json.JsonReader; @@ -14,20 +17,21 @@ public List getSdkConfigurations() { } static NewResponse fromJson(JsonReader jsonReader) throws IOException { - return jsonReader.readObject((reader) -> { - NewResponse deserializedValue = new NewResponse(); - - while(reader.nextToken() != JsonToken.END_OBJECT) { - String fieldName = reader.getFieldName(); - reader.nextToken(); - if ("sdkConfiguration".equals(fieldName)) { - deserializedValue.sdkConfigurations = reader.readArray(SdkConfiguration::fromJson); - } else { - reader.skipChildren(); - } - } - - return deserializedValue; - }); + return jsonReader.readObject( + (reader) -> { + NewResponse deserializedValue = new NewResponse(); + + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + if ("sdkConfiguration".equals(fieldName)) { + deserializedValue.sdkConfigurations = reader.readArray(SdkConfiguration::fromJson); + } else { + reader.skipChildren(); + } + } + + return deserializedValue; + }); } } diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/SdkConfiguration.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/SdkConfiguration.java index 951732c4d54..62e3fba02ab 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/SdkConfiguration.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/SdkConfiguration.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.applicationinsights.agent.internal.keytransaction; import com.azure.json.JsonReader; @@ -18,22 +21,23 @@ public KeyTransactionConfig getValue() { } static SdkConfiguration fromJson(JsonReader jsonReader) throws IOException { - return jsonReader.readObject((reader) -> { - SdkConfiguration deserializedValue = new SdkConfiguration(); - - while (reader.nextToken() != JsonToken.END_OBJECT) { - String fieldName = reader.getFieldName(); - reader.nextToken(); - if ("Key".equals(fieldName)) { - deserializedValue.key = reader.getString(); - } else if ("Value".equals(fieldName)) { - deserializedValue.value = KeyTransactionConfig.fromJson(reader); - } else { - reader.skipChildren(); - } - } - - return deserializedValue; - }); + return jsonReader.readObject( + (reader) -> { + SdkConfiguration deserializedValue = new SdkConfiguration(); + + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + if ("Key".equals(fieldName)) { + deserializedValue.key = reader.getString(); + } else if ("Value".equals(fieldName)) { + deserializedValue.value = KeyTransactionConfig.fromJson(reader); + } else { + reader.skipChildren(); + } + } + + return deserializedValue; + }); } } diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/Samplers.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/Samplers.java index b34b024ca05..4db1eeabf16 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/Samplers.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/Samplers.java @@ -5,8 +5,8 @@ import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; import com.microsoft.applicationinsights.agent.internal.configuration.Configuration.SamplingOverride; -import com.microsoft.applicationinsights.agent.internal.keytransaction.KeyTransactionSampler; import com.microsoft.applicationinsights.agent.internal.keytransaction.KeyTransactionConfigSupplier; +import com.microsoft.applicationinsights.agent.internal.keytransaction.KeyTransactionSampler; import io.opentelemetry.sdk.trace.samplers.Sampler; import java.util.List; import java.util.stream.Collectors; @@ -51,7 +51,8 @@ public static Sampler getSampler( if (samplingPreview.parentBased) { // when using parent-based sampling, sampling overrides still take precedence - // IMPORTANT, the parent-based sampler is useful for interop with other sampling mechanisms, as + // IMPORTANT, the parent-based sampler is useful for interop with other sampling mechanisms, + // as // it will ensure consistent traces, however it does not accurately compute item counts, since // item counts are not propagated in trace state (yet) return Sampler.parentBasedBuilder(sampler).build(); diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/telemetry/TelemetryClient.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/telemetry/TelemetryClient.java index 20c1b991404..7126228d8e1 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/telemetry/TelemetryClient.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/telemetry/TelemetryClient.java @@ -257,8 +257,9 @@ private BatchItemProcessor initBatchItemProcessor( } if (KeyTransactionConfigSupplier.KEY_TRANSACTIONS_ENABLED) { - telemetryPipelineListener = TelemetryPipelineListener.composite(telemetryPipelineListener, - new KeyTransactionTelemetryPipelineListener()); + telemetryPipelineListener = + TelemetryPipelineListener.composite( + telemetryPipelineListener, new KeyTransactionTelemetryPipelineListener()); } return BatchItemProcessor.builder( diff --git a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java index e1f7ddae26b..3254b815bc8 100644 --- a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java +++ b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.applicationinsights.agent.internal.keytransaction; import static com.microsoft.applicationinsights.agent.internal.keytransaction.KeyTransactionConfig.Operator.EQUALS; diff --git a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceStateTest.java b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceStateTest.java index b0417108ee2..f5a6ad826e4 100644 --- a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceStateTest.java +++ b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionTraceStateTest.java @@ -1,12 +1,14 @@ -package com.microsoft.applicationinsights.agent.internal.keytransaction; - -import org.junit.jupiter.api.Test; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -import java.util.Map; +package com.microsoft.applicationinsights.agent.internal.keytransaction; import static com.microsoft.applicationinsights.agent.internal.keytransaction.KeyTransactionTraceState.getKeyTransactionStartTimes; import static org.assertj.core.api.Assertions.assertThat; +import java.util.Map; +import org.junit.jupiter.api.Test; + class KeyTransactionTraceStateTest { @Test From 8a25f31ed23b13d3a379b903b9d77ec39b99c374 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 23 Sep 2024 14:41:34 -0700 Subject: [PATCH 4/6] Fix --- .../keytransaction/KeyTransactionConfigSupplier.java | 5 ++++- .../keytransaction/KeyTransactionSpanProcessor.java | 2 +- .../keytransaction/KeyTransactionConfigTest.java | 10 +++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java index b8e28b55ecb..25cb0886ea0 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigSupplier.java @@ -3,6 +3,7 @@ package com.microsoft.applicationinsights.agent.internal.keytransaction; +import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; import com.azure.json.JsonProviders; @@ -28,7 +29,9 @@ public static KeyTransactionConfigSupplier getInstance() { return instance; } - private volatile List configs; + private volatile List configs = emptyList(); + + private KeyTransactionConfigSupplier() {} @Override public List get() { diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSpanProcessor.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSpanProcessor.java index 83e858d0307..fc8fb459bf0 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSpanProcessor.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSpanProcessor.java @@ -23,7 +23,7 @@ public class KeyTransactionSpanProcessor implements SpanProcessor { // TODO remove global state - public static volatile DoubleHistogram keyTransactionHistogram; + private static volatile DoubleHistogram keyTransactionHistogram; public static void initMeterProvider(MeterProvider meterProvider) { keyTransactionHistogram = diff --git a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java index 3254b815bc8..0e31688a080 100644 --- a/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java +++ b/agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionConfigTest.java @@ -28,9 +28,9 @@ void test() throws Exception { assertThat(earthOrbitKeyTransactionConfig.getStartCriteria()).hasSize(1); KeyTransactionConfig.Criterion earthOrbitStartCriteria = earthOrbitKeyTransactionConfig.getStartCriteria().get(0); - assertThat(earthOrbitStartCriteria.getField().getKey()).isEqualTo("http.path"); + assertThat(earthOrbitStartCriteria.getField().getKey()).isEqualTo("url.path"); assertThat(earthOrbitStartCriteria.getOperator()).isEqualTo(EQUALS); - assertThat(earthOrbitStartCriteria.getValue()).isEqualTo("earth"); + assertThat(earthOrbitStartCriteria.getValue()).isEqualTo("/earth"); assertThat(earthOrbitKeyTransactionConfig.getEndCriteria()).isEmpty(); @@ -43,9 +43,9 @@ void test() throws Exception { assertThat(marsMissionKeyTransactionConfig.getStartCriteria()).hasSize(1); KeyTransactionConfig.Criterion marsMissionStartCriteria = marsMissionKeyTransactionConfig.getStartCriteria().get(0); - assertThat(marsMissionStartCriteria.getField().getKey()).isEqualTo("http.path"); + assertThat(marsMissionStartCriteria.getField().getKey()).isEqualTo("url.path"); assertThat(marsMissionStartCriteria.getOperator()).isEqualTo(EQUALS); - assertThat(marsMissionStartCriteria.getValue()).isEqualTo("mars"); + assertThat(marsMissionStartCriteria.getValue()).isEqualTo("/mars"); assertThat(marsMissionKeyTransactionConfig.getEndCriteria()).hasSize(2); @@ -56,7 +56,7 @@ void test() throws Exception { assertThat(marsMissionEndCriteria1.getValue()).isEqualTo("process"); KeyTransactionConfig.Criterion marsMissionEndCriteria2 = - marsMissionKeyTransactionConfig.getEndCriteria().get(0); + marsMissionKeyTransactionConfig.getEndCriteria().get(1); assertThat(marsMissionEndCriteria2.getField().getKey()).isEqualTo("messaging.destination.name"); assertThat(marsMissionEndCriteria2.getOperator()).isEqualTo(EQUALS); assertThat(marsMissionEndCriteria2.getValue()).isEqualTo("space"); From 15fb1ce5da3ab47d76f20a19fc8bd7c4316f8f0e Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 23 Sep 2024 15:03:22 -0700 Subject: [PATCH 5/6] Fix --- .../agent/internal/keytransaction/KeyTransactionSampler.java | 3 +++ .../applicationinsights/agent/internal/sampling/Samplers.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSampler.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSampler.java index f504af0432f..d9e67db3211 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSampler.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/keytransaction/KeyTransactionSampler.java @@ -84,6 +84,9 @@ public SamplingResult shouldSample( SamplingResult result = fallback.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks); + if (existingKeyTransactions.isEmpty() && startKeyTransactions.isEmpty()) { + return result; + } return new TransactionSamplingResult( existingKeyTransactions, startKeyTransactions, endKeyTransactions, result); } diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/Samplers.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/Samplers.java index 4db1eeabf16..a55385e961f 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/Samplers.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/sampling/Samplers.java @@ -55,7 +55,7 @@ public static Sampler getSampler( // as // it will ensure consistent traces, however it does not accurately compute item counts, since // item counts are not propagated in trace state (yet) - return Sampler.parentBasedBuilder(sampler).build(); + sampler = Sampler.parentBasedBuilder(sampler).build(); } if (KeyTransactionConfigSupplier.KEY_TRANSACTIONS_ENABLED) { From 8a65b6844da4462bf0affcbf0a51066aa7d8027c Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 23 Sep 2024 17:08:25 -0700 Subject: [PATCH 6/6] Fix --- .../fakeingestion/MockedAppInsightsIngestionServlet.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/fakeingestion/MockedAppInsightsIngestionServlet.java b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/fakeingestion/MockedAppInsightsIngestionServlet.java index 6fdbb3d6a0b..5f0b6f95467 100644 --- a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/fakeingestion/MockedAppInsightsIngestionServlet.java +++ b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/fakeingestion/MockedAppInsightsIngestionServlet.java @@ -138,6 +138,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I type2envelope.put(baseType, envelope); } } + + resp.getWriter().print("{}"); } @Override