diff --git a/agent/agent-bootstrap/src/main/java/com/microsoft/applicationinsights/agent/bootstrap/PreAggregatedStandardMetrics.java b/agent/agent-bootstrap/src/main/java/com/microsoft/applicationinsights/agent/bootstrap/PreAggregatedStandardMetrics.java new file mode 100644 index 00000000000..f121648e52a --- /dev/null +++ b/agent/agent-bootstrap/src/main/java/com/microsoft/applicationinsights/agent/bootstrap/PreAggregatedStandardMetrics.java @@ -0,0 +1,118 @@ +/* + * ApplicationInsights-Java + * Copyright (c) Microsoft Corporation + * All rights reserved. + * + * MIT License + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the ""Software""), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.microsoft.applicationinsights.agent.bootstrap; + +import static io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes.IS_PRE_AGGREGATED; +import static io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes.IS_SYNTHETIC; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes; +import io.opentelemetry.instrumentation.api.instrumenter.UserAgents; +import javax.annotation.Nullable; + +public class PreAggregatedStandardMetrics { + + @Nullable private static volatile AttributeGetter attributeGetter; + + public static void setAttributeGetter(AttributeGetter attributeGetter) { + PreAggregatedStandardMetrics.attributeGetter = attributeGetter; + } + + public static void applyHttpClientView( + AttributesBuilder builder, + Context context, + Attributes startAttributes, + Attributes endAttributes) { + + Span span = Span.fromContext(context); + applyCommon(builder, span); + } + + public static void applyHttpServerView( + AttributesBuilder builder, + Context context, + Attributes startAttributes, + Attributes endAttributes) { + + Span span = Span.fromContext(context); + applyCommon(builder, span); + + // is_synthetic is only applied to server requests + builder.put(IS_SYNTHETIC, UserAgents.isBot(endAttributes, startAttributes)); + } + + public static void applyRpcClientView( + AttributesBuilder builder, + Context context, + Attributes startAttributes, + Attributes endAttributes) { + + applyHttpClientView(builder, context, startAttributes, endAttributes); + } + + public static void applyRpcServerView( + AttributesBuilder builder, + Context context, + Attributes startAttributes, + Attributes endAttributes) { + + applyHttpServerView(builder, context, startAttributes, endAttributes); + } + + private static void applyCommon(AttributesBuilder builder, Span span) { + + // this is needed for detecting telemetry signals that will trigger pre-aggregated metrics via + // auto instrumentations + span.setAttribute(IS_PRE_AGGREGATED, true); + + if (attributeGetter == null) { + return; + } + String connectionString = + attributeGetter.get(span, BootstrapSemanticAttributes.CONNECTION_STRING); + if (connectionString != null) { + builder.put(BootstrapSemanticAttributes.CONNECTION_STRING, connectionString); + } else { + // back compat support + String instrumentationKey = + attributeGetter.get(span, BootstrapSemanticAttributes.INSTRUMENTATION_KEY); + if (instrumentationKey != null) { + builder.put(BootstrapSemanticAttributes.INSTRUMENTATION_KEY, instrumentationKey); + } + } + String roleName = attributeGetter.get(span, BootstrapSemanticAttributes.ROLE_NAME); + if (roleName != null) { + builder.put(BootstrapSemanticAttributes.ROLE_NAME, roleName); + } + } + + @FunctionalInterface + public interface AttributeGetter { + T get(Span span, AttributeKey key); + } + + private PreAggregatedStandardMetrics() {} +} diff --git a/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/BootstrapSemanticAttributes.java b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/BootstrapSemanticAttributes.java index ec78b6fac05..3c7fc995866 100644 --- a/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/BootstrapSemanticAttributes.java +++ b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/BootstrapSemanticAttributes.java @@ -27,6 +27,17 @@ public final class BootstrapSemanticAttributes { + // replaced by ai.preview.connection_string + @Deprecated + public static final AttributeKey INSTRUMENTATION_KEY = + AttributeKey.stringKey("ai.preview.instrumentation_key"); + + public static final AttributeKey CONNECTION_STRING = + AttributeKey.stringKey("ai.preview.connection_string"); + + public static final AttributeKey ROLE_NAME = + AttributeKey.stringKey("ai.preview.service_name"); + public static final AttributeKey IS_SYNTHETIC = booleanKey("applicationinsights.internal.is_synthetic"); public static final AttributeKey IS_PRE_AGGREGATED = diff --git a/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetrics.java b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetrics.java index 9ec4e740be5..0c3c4f248ee 100644 --- a/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetrics.java +++ b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetrics.java @@ -19,10 +19,15 @@ * DEALINGS IN THE SOFTWARE. */ +// Includes work from: +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.api.instrumenter.http; -import static io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes.IS_PRE_AGGREGATED; -import static io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes.IS_SYNTHETIC; +import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyClientDurationAndSizeView; import static java.util.logging.Level.FINE; import com.google.auto.value.AutoValue; @@ -31,12 +36,10 @@ import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.api.metrics.LongHistogram; import io.opentelemetry.api.metrics.Meter; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; -import io.opentelemetry.instrumentation.api.instrumenter.UserAgents; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; @@ -109,25 +112,16 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { context); return; } - Attributes durationAndSizeAttributes = - TemporaryMetricsView.applyClientDurationAndSizeView(state.startAttributes(), endAttributes); - // START APPLICATION INSIGHTS CODE + // START APPLICATION INSIGHTS MODIFICATIONS: passing context - // this is needed for detecting telemetry signals that will trigger pre-aggregated metrics via - // auto instrumentations - Span.fromContext(context).setAttribute(IS_PRE_AGGREGATED, true); - - Attributes durationAttributes = - durationAndSizeAttributes.toBuilder() - .put(IS_SYNTHETIC, UserAgents.isBot(endAttributes, state.startAttributes())) - .build(); - - // END APPLICATION INSIGHTS CODE + Attributes durationAndSizeAttributes = + applyClientDurationAndSizeView(context, state.startAttributes(), endAttributes); - this.duration.record( - (endNanos - state.startTimeNanos()) / NANOS_PER_MS, durationAttributes, context); + // END APPLICATION MODIFICATIONS CODE + duration.record( + (endNanos - state.startTimeNanos()) / NANOS_PER_MS, durationAndSizeAttributes, context); Long requestLength = getAttribute( SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, endAttributes, state.startAttributes()); diff --git a/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetrics.java b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetrics.java index 74441bba767..7d0f36756a5 100644 --- a/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetrics.java +++ b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpServerMetrics.java @@ -19,10 +19,16 @@ * DEALINGS IN THE SOFTWARE. */ +// Includes work from: +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.api.instrumenter.http; -import static io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes.IS_PRE_AGGREGATED; -import static io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes.IS_SYNTHETIC; +import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyActiveRequestsView; +import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyServerDurationAndSizeView2; import static java.util.logging.Level.FINE; import com.google.auto.value.AutoValue; @@ -32,12 +38,10 @@ import io.opentelemetry.api.metrics.LongHistogram; import io.opentelemetry.api.metrics.LongUpDownCounter; import io.opentelemetry.api.metrics.Meter; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; -import io.opentelemetry.instrumentation.api.instrumenter.UserAgents; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; @@ -103,7 +107,7 @@ private HttpServerMetrics(Meter meter) { @Override public Context onStart(Context context, Attributes startAttributes, long startNanos) { - activeRequests.add(1, TemporaryMetricsView.applyActiveRequestsView(startAttributes), context); + activeRequests.add(1, applyActiveRequestsView(startAttributes), context); return context.with( HTTP_SERVER_REQUEST_METRICS_STATE, @@ -120,27 +124,17 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { context); return; } - activeRequests.add( - -1, TemporaryMetricsView.applyActiveRequestsView(state.startAttributes()), context); - Attributes durationAndSizeAttributes = - TemporaryMetricsView.applyServerDurationAndSizeView(state.startAttributes(), endAttributes); + activeRequests.add(-1, applyActiveRequestsView(state.startAttributes()), context); - // START APPLICATION INSIGHTS CODE + // START APPLICATION INSIGHTS MODIFICATIONS: passing context - // this is needed for detecting telemetry signals that will trigger pre-aggregated metrics via - // auto instrumentations - Span.fromContext(context).setAttribute(IS_PRE_AGGREGATED, true); - - Attributes durationAttributes = - durationAndSizeAttributes.toBuilder() - .put(IS_SYNTHETIC, UserAgents.isBot(endAttributes, state.startAttributes())) - .build(); + Attributes durationAndSizeAttributes = + applyServerDurationAndSizeView2(context, state.startAttributes(), endAttributes); // END APPLICATION INSIGHTS CODE - this.duration.record( - (endNanos - state.startTimeNanos()) / NANOS_PER_MS, durationAttributes, context); - + duration.record( + (endNanos - state.startTimeNanos()) / NANOS_PER_MS, durationAndSizeAttributes, context); Long requestLength = getAttribute( SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, endAttributes, state.startAttributes()); diff --git a/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsView.java b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsView.java new file mode 100644 index 00000000000..fe87d9f819b --- /dev/null +++ b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/TemporaryMetricsView.java @@ -0,0 +1,147 @@ +/* + * ApplicationInsights-Java + * Copyright (c) Microsoft Corporation + * All rights reserved. + * + * MIT License + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the ""Software""), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +// Includes work from: +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.http; + +import com.microsoft.applicationinsights.agent.bootstrap.PreAggregatedStandardMetrics; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.HashSet; +import java.util.Set; +import java.util.function.BiConsumer; + +// this is temporary, see +// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/3962#issuecomment-906606325 +@SuppressWarnings("rawtypes") +final class TemporaryMetricsView { + + private static final Set durationAlwaysInclude = buildDurationAlwaysInclude(); + private static final Set durationClientView = buildDurationClientView(); + private static final Set durationServerView = buildDurationServerView(); + private static final Set activeRequestsView = buildActiveRequestsView(); + + private static Set buildDurationAlwaysInclude() { + // the list of included metrics is from + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#attributes + Set view = new HashSet<>(); + // START APPLICATION INSIGHTS MODIFICATIONS + // view.add(SemanticAttributes.HTTP_METHOD); + view.add(SemanticAttributes.HTTP_STATUS_CODE); // Optional + // view.add(SemanticAttributes.HTTP_FLAVOR); // Optional + // END APPLICATION INSIGHTS MODIFICATIONS + return view; + } + + private static Set buildDurationClientView() { + // We pull identifying attributes according to: + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#attribute-alternatives + // We only pull net.peer.name and net.peer.port because http.url has too high cardinality + Set view = new HashSet<>(durationAlwaysInclude); + view.add(SemanticAttributes.NET_PEER_NAME); + view.add(SemanticAttributes.NET_PEER_PORT); + return view; + } + + private static Set buildDurationServerView() { + // We pull identifying attributes according to: + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#attribute-alternatives + // With the following caveat: + // - we always rely on http.route + http.host in this repository. + // - we prefer http.route (which is scrubbed) over http.target (which is not scrubbed). + Set view = new HashSet<>(durationAlwaysInclude); + // START APPLICATION INSIGHTS MODIFICATIONS + // view.add(SemanticAttributes.HTTP_SCHEME); + // view.add(SemanticAttributes.HTTP_HOST); + // view.add(SemanticAttributes.HTTP_ROUTE); + // END APPLICATION INSIGHTS MODIFICATIONS + return view; + } + + private static Set buildActiveRequestsView() { + // the list of included metrics is from + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#attributes + Set view = new HashSet<>(); + view.add(SemanticAttributes.HTTP_METHOD); + view.add(SemanticAttributes.HTTP_HOST); + view.add(SemanticAttributes.HTTP_SCHEME); + view.add(SemanticAttributes.HTTP_FLAVOR); + view.add(SemanticAttributes.HTTP_SERVER_NAME); + return view; + } + + // START APPLICATION INSIGHTS MODIFICATIONS + + static Attributes applyClientDurationAndSizeView( + Context context, Attributes startAttributes, Attributes endAttributes) { + AttributesBuilder filtered = Attributes.builder(); + applyView(filtered, startAttributes, durationClientView); + applyView(filtered, endAttributes, durationClientView); + + PreAggregatedStandardMetrics.applyHttpClientView( + filtered, context, startAttributes, endAttributes); + + return filtered.build(); + } + + // had to add "2" to this method name because sometimes compilation picks up the upstream version + static Attributes applyServerDurationAndSizeView2( + Context context, Attributes startAttributes, Attributes endAttributes) { + AttributesBuilder filtered = Attributes.builder(); + applyView(filtered, startAttributes, durationServerView); + applyView(filtered, endAttributes, durationServerView); + + PreAggregatedStandardMetrics.applyHttpServerView( + filtered, context, startAttributes, endAttributes); + + return filtered.build(); + } + + // END APPLICATION INSIGHTS MODIFICATIONS + + static Attributes applyActiveRequestsView(Attributes attributes) { + AttributesBuilder filtered = Attributes.builder(); + applyView(filtered, attributes, activeRequestsView); + return filtered.build(); + } + + @SuppressWarnings("unchecked") + private static void applyView( + AttributesBuilder filtered, Attributes attributes, Set view) { + attributes.forEach( + (BiConsumer) + (key, value) -> { + if (view.contains(key)) { + filtered.put(key, value); + } + }); + } + + private TemporaryMetricsView() {} +} diff --git a/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsView.java b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsView.java new file mode 100644 index 00000000000..03f050a9f44 --- /dev/null +++ b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/MetricsView.java @@ -0,0 +1,150 @@ +/* + * ApplicationInsights-Java + * Copyright (c) Microsoft Corporation + * All rights reserved. + * + * MIT License + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the ""Software""), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +// Includes work from: +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter.rpc; + +import com.microsoft.applicationinsights.agent.bootstrap.PreAggregatedStandardMetrics; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.HashSet; +import java.util.Set; +import java.util.function.BiConsumer; + +// this is temporary, see +// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/3962#issuecomment-906606325 +@SuppressWarnings("rawtypes") +final class MetricsView { + + private static final Set alwaysInclude = buildAlwaysInclude(); + private static final Set clientView = buildClientView(); + private static final Set serverView = buildServerView(); + private static final Set serverFallbackView = buildServerFallbackView(); + + private static Set buildAlwaysInclude() { + // the list of recommended metrics attributes is from + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc.md#attributes + Set view = new HashSet<>(); + view.add(SemanticAttributes.RPC_SYSTEM); + // START APPLICATION INSIGHTS MODIFICATIONS + // view.add(SemanticAttributes.RPC_SERVICE); + // view.add(SemanticAttributes.RPC_METHOD); + // END APPLICATION INSIGHTS MODIFICATIONS + return view; + } + + private static Set buildClientView() { + // the list of rpc client metrics attributes is from + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc.md#attributes + Set view = new HashSet<>(alwaysInclude); + view.add(SemanticAttributes.NET_PEER_NAME); + view.add(SemanticAttributes.NET_PEER_PORT); + // START APPLICATION INSIGHTS MODIFICATIONS + // view.add(SemanticAttributes.NET_TRANSPORT); + // END APPLICATION INSIGHTS MODIFICATIONS + return view; + } + + private static Set buildServerView() { + // the list of rpc server metrics attributes is from + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc.md#attributes + Set view = new HashSet<>(alwaysInclude); + // START APPLICATION INSIGHTS MODIFICATIONS + // view.add(SemanticAttributes.NET_HOST_NAME); + // view.add(SemanticAttributes.NET_TRANSPORT); + // END APPLICATION INSIGHTS MODIFICATIONS + return view; + } + + private static Set buildServerFallbackView() { + // the list of rpc server metrics attributes is from + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/rpc.md#attributes + Set view = new HashSet<>(alwaysInclude); + // START APPLICATION INSIGHTS MODIFICATIONS + // view.add(SemanticAttributes.NET_HOST_IP); + // view.add(SemanticAttributes.NET_TRANSPORT); + // END APPLICATION INSIGHTS MODIFICATIONS + return view; + } + + private static boolean containsAttribute( + AttributeKey key, Attributes startAttributes, Attributes endAttributes) { + return startAttributes.get(key) != null || endAttributes.get(key) != null; + } + + // START APPLICATION INSIGHTS MODIFICATIONS + + static Attributes applyClientView( + Context context, Attributes startAttributes, Attributes endAttributes) { + AttributesBuilder filtered = applyView(clientView, startAttributes, endAttributes); + + PreAggregatedStandardMetrics.applyRpcClientView( + filtered, context, startAttributes, endAttributes); + + return filtered.build(); + } + + static Attributes applyServerView( + Context context, Attributes startAttributes, Attributes endAttributes) { + Set fullSet = serverView; + if (!containsAttribute(SemanticAttributes.NET_HOST_NAME, startAttributes, endAttributes)) { + fullSet = serverFallbackView; + } + AttributesBuilder filtered = applyView(fullSet, startAttributes, endAttributes); + + PreAggregatedStandardMetrics.applyRpcServerView( + filtered, context, startAttributes, endAttributes); + + return filtered.build(); + } + + // END APPLICATION INSIGHTS MODIFICATIONS + + static AttributesBuilder applyView( + Set view, Attributes startAttributes, Attributes endAttributes) { + AttributesBuilder filtered = Attributes.builder(); + applyView(filtered, startAttributes, view); + applyView(filtered, endAttributes, view); + return filtered; + } + + @SuppressWarnings("unchecked") + private static void applyView( + AttributesBuilder filtered, Attributes attributes, Set view) { + attributes.forEach( + (BiConsumer) + (key, value) -> { + if (view.contains(key)) { + filtered.put(key, value); + } + }); + } + + private MetricsView() {} +} diff --git a/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/RpcClientMetrics.java b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/RpcClientMetrics.java index 8feb183fb4f..ee0298c2d17 100644 --- a/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/RpcClientMetrics.java +++ b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/RpcClientMetrics.java @@ -19,22 +19,25 @@ * DEALINGS IN THE SOFTWARE. */ +// Includes work from: +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.api.instrumenter.rpc; -import static io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes.IS_PRE_AGGREGATED; -import static io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes.IS_SYNTHETIC; +import static io.opentelemetry.instrumentation.api.instrumenter.rpc.MetricsView.applyClientView; import static java.util.logging.Level.FINE; import com.google.auto.value.AutoValue; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.api.metrics.Meter; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; -import io.opentelemetry.instrumentation.api.instrumenter.UserAgents; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; @@ -47,7 +50,7 @@ public final class RpcClientMetrics implements OperationListener { private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1); - private static final ContextKey RPC_CLIENT_REQUEST_METRICS_STATE = + private static final ContextKey RPC_CLIENT_REQUEST_METRICS_STATE = ContextKey.named("rpc-client-request-metrics-state"); private static final Logger logger = Logger.getLogger(RpcClientMetrics.class.getName()); @@ -90,23 +93,14 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { return; } - // START APPLICATION INSIGHTS CODE - - Attributes attributes = MetricsView.applyClientView(state.startAttributes(), endAttributes); - - // this is needed for detecting telemetry signals that will trigger pre-aggregated metrics via - // auto instrumentations - Span.fromContext(context).setAttribute(IS_PRE_AGGREGATED, true); - - attributes = - attributes.toBuilder() - .put(IS_SYNTHETIC, UserAgents.isBot(endAttributes, state.startAttributes())) - .build(); - - // END APPLICATION INSIGHTS CODE + // START APPLICATION INSIGHTS MODIFICATIONS: passing context clientDurationHistogram.record( - (endNanos - state.startTimeNanos()) / NANOS_PER_MS, attributes, context); + (endNanos - state.startTimeNanos()) / NANOS_PER_MS, + applyClientView(context, state.startAttributes(), endAttributes), + context); + + // END APPLICATION INSIGHTS MODIFICATIONS } @AutoValue diff --git a/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/RpcServerMetrics.java b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/RpcServerMetrics.java index f1943f75e18..1500753d103 100644 --- a/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/RpcServerMetrics.java +++ b/agent/agent-bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/rpc/RpcServerMetrics.java @@ -19,22 +19,25 @@ * DEALINGS IN THE SOFTWARE. */ +// Includes work from: +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.api.instrumenter.rpc; -import static io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes.IS_PRE_AGGREGATED; -import static io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes.IS_SYNTHETIC; +import static io.opentelemetry.instrumentation.api.instrumenter.rpc.MetricsView.applyServerView; import static java.util.logging.Level.FINE; import com.google.auto.value.AutoValue; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.api.metrics.Meter; -import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; -import io.opentelemetry.instrumentation.api.instrumenter.UserAgents; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; @@ -47,7 +50,7 @@ public final class RpcServerMetrics implements OperationListener { private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1); - private static final ContextKey RPC_SERVER_REQUEST_METRICS_STATE = + private static final ContextKey RPC_SERVER_REQUEST_METRICS_STATE = ContextKey.named("rpc-server-request-metrics-state"); private static final Logger logger = Logger.getLogger(RpcServerMetrics.class.getName()); @@ -90,23 +93,14 @@ public void onEnd(Context context, Attributes endAttributes, long endNanos) { return; } - // START APPLICATION INSIGHTS CODE - - Attributes attributes = MetricsView.applyServerView(state.startAttributes(), endAttributes); - - // this is needed for detecting telemetry signals that will trigger pre-aggregated metrics via - // auto instrumentations - Span.fromContext(context).setAttribute(IS_PRE_AGGREGATED, true); - - attributes = - attributes.toBuilder() - .put(IS_SYNTHETIC, UserAgents.isBot(endAttributes, state.startAttributes())) - .build(); - - // END APPLICATION INSIGHTS CODE + // START APPLICATION INSIGHTS MODIFICATIONS: passing context serverDurationHistogram.record( - (endNanos - state.startTimeNanos()) / NANOS_PER_MS, attributes, context); + (endNanos - state.startTimeNanos()) / NANOS_PER_MS, + applyServerView(context, state.startAttributes(), endAttributes), + context); + + // END APPLICATION INSIGHTS MODIFICATIONS } @AutoValue diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/InheritedConnectionStringLogProcessor.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/InheritedConnectionStringLogProcessor.java index 09bc9a568e7..f5bf33850f0 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/InheritedConnectionStringLogProcessor.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/InheritedConnectionStringLogProcessor.java @@ -21,7 +21,7 @@ package com.microsoft.applicationinsights.agent.internal.init; -import io.opentelemetry.api.common.AttributeKey; +import com.azure.monitor.opentelemetry.exporter.implementation.AiSemanticAttributes; import io.opentelemetry.api.trace.Span; import io.opentelemetry.sdk.logs.LogProcessor; import io.opentelemetry.sdk.logs.ReadWriteLogRecord; @@ -29,9 +29,6 @@ public class InheritedConnectionStringLogProcessor implements LogProcessor { - private static final AttributeKey CONNECTION_STRING = - AttributeKey.stringKey("ai.preview.connection_string"); - @Override public void onEmit(ReadWriteLogRecord logRecord) { Span currentSpan = Span.current(); @@ -39,9 +36,17 @@ public void onEmit(ReadWriteLogRecord logRecord) { return; } ReadableSpan currentReadableSpan = (ReadableSpan) currentSpan; - String instrumentationKey = currentReadableSpan.getAttribute(CONNECTION_STRING); - if (instrumentationKey != null) { - logRecord.setAttribute(CONNECTION_STRING, instrumentationKey); + String connectionString = + currentReadableSpan.getAttribute(AiSemanticAttributes.CONNECTION_STRING); + if (connectionString != null) { + logRecord.setAttribute(AiSemanticAttributes.CONNECTION_STRING, connectionString); + } else { + // back compat support + String instrumentationKey = + currentReadableSpan.getAttribute(AiSemanticAttributes.INSTRUMENTATION_KEY); + if (instrumentationKey != null) { + logRecord.setAttribute(AiSemanticAttributes.INSTRUMENTATION_KEY, instrumentationKey); + } } } } diff --git a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/InheritedConnectionStringSpanProcessor.java b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/InheritedConnectionStringSpanProcessor.java index 1397ede73b0..086b6e6768c 100644 --- a/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/InheritedConnectionStringSpanProcessor.java +++ b/agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/InheritedConnectionStringSpanProcessor.java @@ -21,8 +21,8 @@ package com.microsoft.applicationinsights.agent.internal.init; +import com.azure.monitor.opentelemetry.exporter.implementation.AiSemanticAttributes; import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.context.Context; @@ -34,9 +34,6 @@ public class InheritedConnectionStringSpanProcessor implements SpanProcessor { - private static final AttributeKey CONNECTION_STRING = - AttributeKey.stringKey("ai.preview.connection_string"); - private final List overrides; public InheritedConnectionStringSpanProcessor( @@ -57,7 +54,7 @@ public void onStart(Context parentContext, ReadWriteSpan span) { } for (Configuration.ConnectionStringOverride override : overrides) { if (target.startsWith(override.httpPathPrefix)) { - span.setAttribute(CONNECTION_STRING, override.connectionString); + span.setAttribute(AiSemanticAttributes.CONNECTION_STRING, override.connectionString); break; } } @@ -68,9 +65,17 @@ public void onStart(Context parentContext, ReadWriteSpan span) { } ReadableSpan parentReadableSpan = (ReadableSpan) parentSpan; - String instrumentationKey = parentReadableSpan.getAttribute(CONNECTION_STRING); - if (instrumentationKey != null) { - span.setAttribute(CONNECTION_STRING, instrumentationKey); + String connectionString = + parentReadableSpan.getAttribute(AiSemanticAttributes.CONNECTION_STRING); + if (connectionString != null) { + span.setAttribute(AiSemanticAttributes.CONNECTION_STRING, connectionString); + } else { + // back compat support + String instrumentationKey = + parentReadableSpan.getAttribute(AiSemanticAttributes.INSTRUMENTATION_KEY); + if (instrumentationKey != null) { + span.setAttribute(AiSemanticAttributes.INSTRUMENTATION_KEY, instrumentationKey); + } } } 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 74a1a87c845..7aaacb2db7e 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 @@ -37,6 +37,7 @@ import com.google.auto.service.AutoService; import com.microsoft.applicationinsights.agent.bootstrap.AiAppId; import com.microsoft.applicationinsights.agent.bootstrap.AzureFunctions; +import com.microsoft.applicationinsights.agent.bootstrap.PreAggregatedStandardMetrics; import com.microsoft.applicationinsights.agent.internal.classicsdk.BytecodeUtilImpl; import com.microsoft.applicationinsights.agent.internal.common.FriendlyException; import com.microsoft.applicationinsights.agent.internal.configuration.Configuration; @@ -62,8 +63,10 @@ import com.microsoft.applicationinsights.agent.internal.telemetry.MetricFilter; import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryClient; import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryObservers; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.trace.Span; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; @@ -75,6 +78,8 @@ import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import io.opentelemetry.sdk.metrics.export.MetricReader; import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReaderBuilder; +import io.opentelemetry.sdk.trace.ReadableSpan; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; @@ -108,6 +113,8 @@ public class SecondEntryPoint implements AutoConfigurationCustomizerProvider { @Override public void customize(AutoConfigurationCustomizer autoConfiguration) { + PreAggregatedStandardMetrics.setAttributeGetter(new AttributeGetterImpl()); + File tempDir = TempDirs.getApplicationInsightsTempDir( startupLogger, @@ -328,6 +335,7 @@ private static SdkTracerProviderBuilder configureTracing( } // adding this even if there are no connectionStringOverrides, in order to support // "ai.preview.connection_string" being set programmatically on CONSUMER spans + // (or "ai.preview.instrumentation_key" for backwards compatibility) tracerProvider.addSpanProcessor( new InheritedConnectionStringSpanProcessor( configuration.preview.connectionStringOverrides)); @@ -457,6 +465,7 @@ private static SdkLogEmitterProviderBuilder configureLogging( } // adding this even if there are no connectionStringOverrides, in order to support // "ai.preview.connection_string" being set programmatically on CONSUMER spans + // (or "ai.preview.instrumentation_key" for backwards compatibility) builder.addLogProcessor(new InheritedConnectionStringLogProcessor()); // adding this even if there are no roleNameOverrides, in order to support // "ai.preview.service_name" being set programmatically on CONSUMER spans @@ -562,12 +571,15 @@ private static SdkMeterProviderBuilder configureMetrics( MetricDataMapper mapper = new MetricDataMapper( telemetryClient::populateDefaults, configuration.preview.captureHttpServer4xxAsError); - metricReader = + PeriodicMetricReaderBuilder readerBuilder = PeriodicMetricReader.builder( - new AgentMetricExporter( - metricFilters, mapper, telemetryClient.getMetricsBatchItemProcessor())) - .setInterval(Duration.ofSeconds(configuration.preview.metricIntervalSeconds)) - .build(); + new AgentMetricExporter( + metricFilters, mapper, telemetryClient.getMetricsBatchItemProcessor())); + int intervalMillis = + Integer.getInteger( + "applicationinsights.testing.metric-reader-interval-millis", + configuration.preview.metricIntervalSeconds * 1000); + metricReader = readerBuilder.setInterval(Duration.ofMillis(intervalMillis)).build(); return builder.registerMetricReader(metricReader); } @@ -614,4 +626,14 @@ public CompletableResultCode shutdown() { return delegate.shutdown(); } } + + public static class AttributeGetterImpl implements PreAggregatedStandardMetrics.AttributeGetter { + @Override + public T get(Span span, AttributeKey key) { + if (span instanceof ReadableSpan) { + return ((ReadableSpan) span).getAttribute(key); + } + return null; + } + } } diff --git a/agent/agent/build.gradle.kts b/agent/agent/build.gradle.kts index 158a3b5f0c9..c0999f0ce79 100644 --- a/agent/agent/build.gradle.kts +++ b/agent/agent/build.gradle.kts @@ -115,9 +115,11 @@ tasks { exclude("io/opentelemetry/javaagent/shaded/instrumentation/api/instrumenter/http/HttpClientMetrics.class") exclude("io/opentelemetry/javaagent/shaded/instrumentation/api/instrumenter/http/HttpServerMetrics.class") + exclude("io/opentelemetry/javaagent/shaded/instrumentation/api/instrumenter/http/TemporaryMetricsView.class") exclude("io/opentelemetry/javaagent/shaded/instrumentation/api/instrumenter/rpc/RpcClientMetrics.class") exclude("io/opentelemetry/javaagent/shaded/instrumentation/api/instrumenter/rpc/RpcServerMetrics.class") + exclude("io/opentelemetry/javaagent/shaded/instrumentation/api/instrumenter/rpc/MetricsView.class") dependsOn(isolateJavaagentLibs) from(isolateJavaagentLibs.get().outputs) diff --git a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/MetricDataMapper.java b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/MetricDataMapper.java index ef2882c68e2..7ead77e6849 100644 --- a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/MetricDataMapper.java +++ b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/MetricDataMapper.java @@ -161,6 +161,8 @@ public static void updateMetricPointBuilder( } pointBuilder.setValue(pointDataValue); + // TODO (heya) why give it the same name as otel metric? + // it seems this field doesn't matter and only _MS.MetricId property matters? pointBuilder.setName(metricData.getName()); metricTelemetryBuilder.setMetricPoint(pointBuilder); @@ -169,6 +171,12 @@ public static void updateMetricPointBuilder( Long statusCode = attributes.get(SemanticAttributes.HTTP_STATUS_CODE); boolean success = isSuccess(statusCode, captureHttpServer4xxAsError); Boolean isSynthetic = attributes.get(IS_SYNTHETIC); + + attributes.forEach( + (key, value) -> + SpanDataMapper.applyConnectionStringAndRoleNameOverrides( + metricTelemetryBuilder, value, key.getKey())); + if (metricData.getName().contains(".server.")) { RequestExtractor.extract(metricTelemetryBuilder, statusCode, success, isSynthetic); } else if (metricData.getName().contains(".client.")) { @@ -190,11 +198,26 @@ public static void updateMetricPointBuilder( metricTelemetryBuilder, statusCode, success, dependencyType, target, isSynthetic); } } else { - attributes.forEach( - (key, value) -> metricTelemetryBuilder.addProperty(key.getKey(), value.toString())); + setExtraAttributes(metricTelemetryBuilder, attributes); } } + private static void setExtraAttributes( + AbstractTelemetryBuilder telemetryBuilder, Attributes attributes) { + attributes.forEach( + (key, value) -> { + String stringKey = key.getKey(); + if (SpanDataMapper.applyConnectionStringAndRoleNameOverrides( + telemetryBuilder, value, stringKey)) { + return; + } + String val = SpanDataMapper.convertToString(value, key.getType()); + if (value != null) { + telemetryBuilder.addProperty(key.getKey(), val); + } + }); + } + private static int getDefaultPortForHttpScheme(@Nullable String httpScheme) { if (httpScheme == null) { return Integer.MAX_VALUE; diff --git a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/SpanDataMapper.java b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/SpanDataMapper.java index d1ea682c985..cde25413818 100644 --- a/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/SpanDataMapper.java +++ b/agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/SpanDataMapper.java @@ -898,6 +898,22 @@ static boolean applyCommonTags( telemetryBuilder.addTag(ContextTagKeys.AI_USER_ID.toString(), (String) value); return true; } + if (applyConnectionStringAndRoleNameOverrides(telemetryBuilder, value, key)) { + return true; + } + if (key.equals(AiSemanticAttributes.ROLE_INSTANCE_ID.getKey()) && value instanceof String) { + telemetryBuilder.addTag(ContextTagKeys.AI_CLOUD_ROLE_INSTANCE.toString(), (String) value); + return true; + } + if (key.equals(AiSemanticAttributes.APPLICATION_VERSION.getKey()) && value instanceof String) { + telemetryBuilder.addTag(ContextTagKeys.AI_APPLICATION_VER.toString(), (String) value); + return true; + } + return false; + } + + static boolean applyConnectionStringAndRoleNameOverrides( + AbstractTelemetryBuilder telemetryBuilder, Object value, String key) { if (key.equals(AiSemanticAttributes.CONNECTION_STRING.getKey()) && value instanceof String) { // intentionally letting exceptions from parse bubble up telemetryBuilder.setConnectionString( @@ -915,14 +931,6 @@ static boolean applyCommonTags( telemetryBuilder.addTag(ContextTagKeys.AI_CLOUD_ROLE.toString(), (String) value); return true; } - if (key.equals(AiSemanticAttributes.ROLE_INSTANCE_ID.getKey()) && value instanceof String) { - telemetryBuilder.addTag(ContextTagKeys.AI_CLOUD_ROLE_INSTANCE.toString(), (String) value); - return true; - } - if (key.equals(AiSemanticAttributes.APPLICATION_VERSION.getKey()) && value instanceof String) { - telemetryBuilder.addTag(ContextTagKeys.AI_APPLICATION_VER.toString(), (String) value); - return true; - } return false; } diff --git a/smoke-tests/apps/ConnectionStringOverrides/build.gradle.kts b/smoke-tests/apps/ConnectionStringOverrides/build.gradle.kts index 1b1cfb106e9..eb5b96e032d 100644 --- a/smoke-tests/apps/ConnectionStringOverrides/build.gradle.kts +++ b/smoke-tests/apps/ConnectionStringOverrides/build.gradle.kts @@ -1,7 +1,3 @@ plugins { id("ai.smoke-test-war") } - -dependencies { - implementation("org.hsqldb:hsqldb:2.5.1") -} diff --git a/smoke-tests/apps/ConnectionStringOverrides/src/main/java/com/microsoft/applicationinsights/smoketestapp/ConnectionStringOverridesServlet.java b/smoke-tests/apps/ConnectionStringOverrides/src/main/java/com/microsoft/applicationinsights/smoketestapp/ConnectionStringOverridesServlet.java index b7aaf01869a..6568ca3a03a 100644 --- a/smoke-tests/apps/ConnectionStringOverrides/src/main/java/com/microsoft/applicationinsights/smoketestapp/ConnectionStringOverridesServlet.java +++ b/smoke-tests/apps/ConnectionStringOverrides/src/main/java/com/microsoft/applicationinsights/smoketestapp/ConnectionStringOverridesServlet.java @@ -21,108 +21,45 @@ package com.microsoft.applicationinsights.smoketestapp; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.concurrent.Callable; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.hsqldb.jdbc.JDBCDriver; @WebServlet("/*") public class ConnectionStringOverridesServlet extends HttpServlet { private static final Logger logger = Logger.getLogger("smoketestapp"); - public void init() throws ServletException { - try { - setupHsqldb(); - } catch (Exception e) { - // surprisingly not all application servers seem to log init exceptions to stdout - e.printStackTrace(); - throw new ServletException(e); - } - } - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException { - try { - int statusCode = doGetInternal(req); - resp.getWriter().println(statusCode); - } catch (ServletException e) { - throw e; - } catch (Exception e) { - throw new ServletException(e); - } - } + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { - private int doGetInternal(HttpServletRequest req) throws Exception { String pathInfo = req.getPathInfo(); if (pathInfo.equals("/")) { - return 200; - } else if (pathInfo.startsWith("/app")) { - Connection connection = getHsqldbConnection(); - executeStatement(connection); - connection.close(); + return; + } + if (pathInfo.startsWith("/app")) { + httpUrlConnection("https://mock.codes/200"); logger.info("hello"); - return 200; - } else { - throw new ServletException("Unexpected url: " + pathInfo); + return; } + throw new ServletException("Unexpected url: " + pathInfo); } - private static Connection getHsqldbConnection() throws Exception { - return JDBCDriver.getConnection("jdbc:hsqldb:mem:testdb", null); - } - - private void executeStatement(Connection connection) throws SQLException { - Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery("select * from abc"); - while (rs.next()) {} - rs.close(); - statement.close(); - } - - private static void setupHsqldb() throws Exception { - Connection connection = - getConnection( - new Callable() { - @Override - public Connection call() throws Exception { - return getHsqldbConnection(); - } - }); - setup(connection); - connection.close(); - } - - private static Connection getConnection(Callable callable) throws Exception { - Exception exception; - long startTimeMillis = System.currentTimeMillis(); - do { - try { - return callable.call(); - } catch (Exception e) { - exception = e; - } - } while (System.currentTimeMillis() - startTimeMillis < 30000); - throw exception; - } - - private static void setup(Connection connection) throws SQLException { - Statement statement = connection.createStatement(); - try { - statement.execute("create table abc (xyz varchar(10))"); - statement.execute("insert into abc (xyz) values ('x')"); - statement.execute("insert into abc (xyz) values ('y')"); - statement.execute("insert into abc (xyz) values ('z')"); - } finally { - statement.close(); - } + private void httpUrlConnection(String url) throws IOException { + URL obj = new URL(url); + HttpURLConnection connection = (HttpURLConnection) obj.openConnection(); + InputStream content = connection.getInputStream(); + // drain the content + byte[] buffer = new byte[1024]; + while (content.read(buffer) != -1) {} + content.close(); } } diff --git a/smoke-tests/apps/ConnectionStringOverrides/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/ConnectionStringOverridesTest.java b/smoke-tests/apps/ConnectionStringOverrides/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/ConnectionStringOverridesTest.java index ecd156c4e12..e95b773a6f4 100644 --- a/smoke-tests/apps/ConnectionStringOverrides/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/ConnectionStringOverridesTest.java +++ b/smoke-tests/apps/ConnectionStringOverrides/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/ConnectionStringOverridesTest.java @@ -33,12 +33,15 @@ import static org.assertj.core.api.Assertions.assertThat; import com.microsoft.applicationinsights.smoketest.schemav2.Data; +import com.microsoft.applicationinsights.smoketest.schemav2.DataPoint; import com.microsoft.applicationinsights.smoketest.schemav2.Envelope; import com.microsoft.applicationinsights.smoketest.schemav2.MessageData; +import com.microsoft.applicationinsights.smoketest.schemav2.MetricData; import com.microsoft.applicationinsights.smoketest.schemav2.RemoteDependencyData; import com.microsoft.applicationinsights.smoketest.schemav2.RequestData; import com.microsoft.applicationinsights.smoketest.schemav2.SeverityLevel; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -85,10 +88,11 @@ private static void testApp(String iKey) throws Exception { assertThat(rd.getSuccess()).isTrue(); - assertThat(rdd.getType()).isEqualTo("SQL"); - assertThat(rdd.getTarget()).isEqualTo("hsqldb | testdb"); - assertThat(rdd.getName()).isEqualTo("SELECT testdb.abc"); - assertThat(rdd.getData()).isEqualTo("select * from abc"); + assertThat(rdd.getType()).isEqualTo("Http"); + assertThat(rdd.getTarget()).isEqualTo("mock.codes"); + assertThat(rdd.getName()).isEqualTo("GET /200"); + assertThat(rdd.getData()).isEqualTo("https://mock.codes/200"); + assertThat(rdd.getResultCode()).isEqualTo("200"); assertThat(rdd.getSuccess()).isTrue(); assertThat(md.getMessage()).isEqualTo("hello"); @@ -102,6 +106,72 @@ private static void testApp(String iKey) throws Exception { rd, rdEnvelope, rddEnvelope, "GET /ConnectionStringOverrides/*"); SmokeTestExtension.assertParentChild( rd, rdEnvelope, mdEnvelope, "GET /ConnectionStringOverrides/*"); + + // and metrics too + + List clientMetrics = + testing.mockedIngestion.waitForMetricItems("http.client.duration", 1); + List serverMetrics = + testing.mockedIngestion.waitForMetricItems("http.server.duration", 1); + + verifyHttpClientPreAggregatedMetrics(clientMetrics, iKey); + verifyHttpServerPreAggregatedMetrics(serverMetrics, iKey); + } + + private static void verifyHttpClientPreAggregatedMetrics(List metrics, String iKey) { + assertThat(metrics.size()).isEqualTo(1); + + Envelope envelope1 = metrics.get(0); + assertThat(envelope1.getIKey()).isEqualTo(iKey); + validateTags(envelope1); + MetricData md1 = (MetricData) ((Data) envelope1.getData()).getBaseData(); + validateMetricData("client", md1, "200"); + } + + private static void verifyHttpServerPreAggregatedMetrics(List metrics, String iKey) { + assertThat(metrics.size()).isEqualTo(1); + + Envelope envelope1 = metrics.get(0); + assertThat(envelope1.getIKey()).isEqualTo(iKey); + validateTags(envelope1); + MetricData md1 = (MetricData) ((Data) envelope1.getData()).getBaseData(); + validateMetricData("server", md1, "200"); + } + + private static void validateTags(Envelope envelope) { + Map tags = envelope.getTags(); + assertThat(tags.get("ai.internal.sdkVersion")).isNotNull(); + assertThat(tags).containsEntry("ai.cloud.roleInstance", "testroleinstance"); + assertThat(tags).containsEntry("ai.cloud.role", "testrolename"); + } + + private static void validateMetricData(String type, MetricData metricData, String resultCode) { + List dataPoints = metricData.getMetrics(); + assertThat(dataPoints).hasSize(1); + DataPoint dataPoint = dataPoints.get(0); + assertThat(dataPoint.getCount()).isEqualTo(1); + assertThat(dataPoint.getValue()).isGreaterThan(0d).isLessThan(60 * 1000.0); + assertThat(dataPoint.getMin()).isGreaterThan(0d).isLessThan(60 * 1000.0); + assertThat(dataPoint.getMax()).isGreaterThan(0d).isLessThan(60 * 1000.0); + Map properties = metricData.getProperties(); + String expectedSuccess = "200".equals(resultCode) ? "True" : "False"; + if ("client".equals(type)) { + assertThat(properties).hasSize(9); + assertThat(properties.get("_MS.MetricId")).isEqualTo("dependencies/duration"); + assertThat(properties.get("dependency/resultCode")).isEqualTo(resultCode); + assertThat(properties.get("dependency/success")).isEqualTo(expectedSuccess); + assertThat(properties.get("dependency/target")).isEqualTo("mock.codes"); + assertThat(properties.get("dependency/type")).isEqualTo("Http"); + } else { + assertThat(properties).hasSize(7); + assertThat(properties.get("_MS.MetricId")).isEqualTo("requests/duration"); + assertThat(properties.get("request/resultCode")).isEqualTo(resultCode); + assertThat(properties.get("request/success")).isEqualTo(expectedSuccess); + } + assertThat(properties.get("operation/synthetic")).isEqualTo("False"); + assertThat(properties.get("cloud/roleInstance")).isEqualTo("testroleinstance"); + assertThat(properties.get("cloud/roleName")).isEqualTo("testrolename"); + assertThat(properties.get("_MS.IsAutocollected")).isEqualTo("True"); } @Environment(TOMCAT_8_JAVA_8) diff --git a/smoke-tests/apps/CoreAndFilter2x/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/CoreAndFilter2xTest.java b/smoke-tests/apps/CoreAndFilter2x/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/CoreAndFilter2xTest.java index eb31e1f18c6..9ea2796cb11 100644 --- a/smoke-tests/apps/CoreAndFilter2x/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/CoreAndFilter2xTest.java +++ b/smoke-tests/apps/CoreAndFilter2x/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/CoreAndFilter2xTest.java @@ -179,9 +179,6 @@ void testTrackException() throws Exception { void testHttpRequest() throws Exception { testing.mockedIngestion.waitForItems("RequestData", 5); - int totalItems = testing.mockedIngestion.getItemCount(); - assertThat(totalItems).isEqualTo(5); - // TODO get HttpRequest data envelope and verify value List requests = testing.mockedIngestion.getTelemetryDataByType("RequestData"); @@ -225,7 +222,7 @@ void testHttpRequest() throws Exception { @TargetUri("/trackMetric") void trackMetric() throws Exception { List rdList = testing.mockedIngestion.waitForItems("RequestData", 1); - List mdList = testing.mockedIngestion.waitForItems("MetricData", 1); + List mdList = testing.mockedIngestion.waitForMetricItems("TimeToRespond", 1); Envelope rdEnvelope = rdList.get(0); Envelope mdEnvelope = mdList.get(0); diff --git a/smoke-tests/apps/CoreAndFilter3x/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/CoreAndFilter3xTest.java b/smoke-tests/apps/CoreAndFilter3x/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/CoreAndFilter3xTest.java index 5b99785c62c..8a3e2368d7e 100644 --- a/smoke-tests/apps/CoreAndFilter3x/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/CoreAndFilter3xTest.java +++ b/smoke-tests/apps/CoreAndFilter3x/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/CoreAndFilter3xTest.java @@ -182,9 +182,6 @@ void testTrackException() throws Exception { void testHttpRequest() throws Exception { testing.mockedIngestion.waitForItems("RequestData", 5); - int totalItems = testing.mockedIngestion.getItemCount(); - assertThat(totalItems).isEqualTo(5); - // TODO get HttpRequest data envelope and verify value List requests = testing.mockedIngestion.getTelemetryDataByType("RequestData"); @@ -228,7 +225,7 @@ void testHttpRequest() throws Exception { @TargetUri("/trackMetric") void trackMetric() throws Exception { List rdList = testing.mockedIngestion.waitForItems("RequestData", 1); - List mdList = testing.mockedIngestion.waitForItems("MetricData", 1); + List mdList = testing.mockedIngestion.waitForMetricItems("TimeToRespond", 1); Envelope rdEnvelope = rdList.get(0); Envelope mdEnvelope = mdList.get(0); @@ -261,7 +258,7 @@ void trackMetric() throws Exception { @TargetUri("/trackMetricWithNamespace") void trackMetricWithNamespace() throws Exception { List rdList = testing.mockedIngestion.waitForItems("RequestData", 1); - List mdList = testing.mockedIngestion.waitForItems("MetricData", 1); + List mdList = testing.mockedIngestion.waitForMetricItems("TimeToRespond", 1); Envelope rdEnvelope = rdList.get(0); Envelope mdEnvelope = mdList.get(0); diff --git a/smoke-tests/apps/HeartBeat/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/HeartBeatTest.java b/smoke-tests/apps/HeartBeat/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/HeartBeatTest.java index cdbf1a13838..42b81e6a9ec 100644 --- a/smoke-tests/apps/HeartBeat/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/HeartBeatTest.java +++ b/smoke-tests/apps/HeartBeat/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/HeartBeatTest.java @@ -49,8 +49,7 @@ abstract class HeartBeatTest { @TargetUri("/index.jsp") void testHeartBeat() throws Exception { List metrics = - testing.mockedIngestion.waitForItems( - testing.getMetricPredicate("HeartbeatState"), 2, 30, TimeUnit.SECONDS); + testing.mockedIngestion.waitForMetricItems("HeartbeatState", 2, 30, TimeUnit.SECONDS); assertThat(metrics).hasSize(2); MetricData data = (MetricData) ((Data) metrics.get(0).getData()).getBaseData(); diff --git a/smoke-tests/apps/HttpPreaggregatedMetrics/src/main/java/com/microsoft/applicationinsights/smoketestapp/HttpPreaggregatedMetricsServlet.java b/smoke-tests/apps/HttpPreaggregatedMetrics/src/main/java/com/microsoft/applicationinsights/smoketestapp/HttpPreaggregatedMetricsServlet.java index 18576efc940..4d34976db87 100644 --- a/smoke-tests/apps/HttpPreaggregatedMetrics/src/main/java/com/microsoft/applicationinsights/smoketestapp/HttpPreaggregatedMetricsServlet.java +++ b/smoke-tests/apps/HttpPreaggregatedMetrics/src/main/java/com/microsoft/applicationinsights/smoketestapp/HttpPreaggregatedMetricsServlet.java @@ -35,62 +35,36 @@ public class HttpPreaggregatedMetricsServlet extends HttpServlet { @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException { - try { - doGetInternal(req); - resp.getWriter().println("hi!"); - } catch (ServletException e) { - throw e; - } catch (Exception e) { - throw new ServletException(e); - } - } + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { - // "/httpUrlConnection" - private void doGetInternal(HttpServletRequest req) throws Exception { String pathInfo = req.getPathInfo(); - final ExecuteGetUrl executeGetUrl; - switch (pathInfo) { - case "/": - executeGetUrl = null; - break; - case "/httpUrlConnection": - executeGetUrl = this::httpUrlConnection; - break; - default: - throw new ServletException("Unexpected url: " + pathInfo); + if (pathInfo.equals("/")) { + return; } - - if (executeGetUrl != null) { - executeGetUrl.execute("https://mock.codes/200?q=spaces%20test"); - try { - executeGetUrl.execute("https://mock.codes/404"); - } catch (Exception e) { - // HttpURLConnection throws exception on 404 and 500 - } - try { - executeGetUrl.execute("https://mock.codes/500"); - } catch (Exception e) { - // HttpURLConnection throws exception on 404 and 500 - } + if (!pathInfo.equals("/httpUrlConnection")) { + throw new ServletException("Unexpected url: " + pathInfo); + } + httpUrlConnection("https://mock.codes/200?q=spaces%20test"); + try { + httpUrlConnection("https://mock.codes/404"); + } catch (Exception e) { + // HttpURLConnection throws exception on 404 and 500 + } + try { + httpUrlConnection("https://mock.codes/500"); + } catch (Exception e) { + // HttpURLConnection throws exception on 404 and 500 } } private void httpUrlConnection(String url) throws IOException { URL obj = new URL(url); HttpURLConnection connection = (HttpURLConnection) obj.openConnection(); - // calling getContentType() first, since this triggered a bug previously in the instrumentation - // previously - connection.getContentType(); InputStream content = connection.getInputStream(); // drain the content byte[] buffer = new byte[1024]; while (content.read(buffer) != -1) {} content.close(); } - - @FunctionalInterface - interface ExecuteGetUrl { - void execute(String url) throws Exception; - } } diff --git a/smoke-tests/apps/HttpPreaggregatedMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/HttpPreaggregatedMetricsTest.java b/smoke-tests/apps/HttpPreaggregatedMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/HttpPreaggregatedMetricsTest.java index 4f7239471c9..e3ecbea0d0b 100644 --- a/smoke-tests/apps/HttpPreaggregatedMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/HttpPreaggregatedMetricsTest.java +++ b/smoke-tests/apps/HttpPreaggregatedMetrics/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/HttpPreaggregatedMetricsTest.java @@ -40,7 +40,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -52,22 +51,12 @@ abstract class HttpPreaggregatedMetricsTest { @Test @TargetUri("/httpUrlConnection") void testHttpUrlConnection() throws Exception { - verify(); - } - - private static void verify() throws Exception { - verify("https://mock.codes/200?q=spaces%20test"); - } - - private static void verify(String successUrlWithQueryString) throws Exception { - verifyHttpclientRequestsAndDependencies(successUrlWithQueryString); + verifyHttpclientRequestsAndDependencies("https://mock.codes/200?q=spaces%20test"); List clientMetrics = - testing.mockedIngestion.waitForItems( - SmokeTestExtension.getMetricPredicate("http.client.duration"), 3, 40, TimeUnit.SECONDS); + testing.mockedIngestion.waitForMetricItems("http.client.duration", 3); List serverMetrics = - testing.mockedIngestion.waitForItems( - SmokeTestExtension.getMetricPredicate("http.server.duration"), 2, 40, TimeUnit.SECONDS); + testing.mockedIngestion.waitForMetricItems("http.server.duration", 1); verifyHttpClientPreAggregatedMetrics(clientMetrics); verifyHttpServerPreAggregatedMetrics(serverMetrics); @@ -128,8 +117,7 @@ private static void verifyHttpclientRequestsAndDependencies(String successUrlWit "GET /HttpPreaggregatedMetrics/*"); } - private static void verifyHttpClientPreAggregatedMetrics(List metrics) - throws Exception { + private static void verifyHttpClientPreAggregatedMetrics(List metrics) { assertThat(metrics.size()).isEqualTo(3); // sort metrics based on result code metrics.sort( @@ -158,20 +146,13 @@ private static void verifyHttpClientPreAggregatedMetrics(List metrics) validateMetricData("client", md3, "500"); } - private static void verifyHttpServerPreAggregatedMetrics(List metrics) - throws Exception { - assertThat(metrics.size()).isEqualTo(2); + private static void verifyHttpServerPreAggregatedMetrics(List metrics) { + assertThat(metrics.size()).isEqualTo(1); // 1st pre-aggregated metric Envelope envelope1 = metrics.get(0); validateTags(envelope1); MetricData md1 = (MetricData) ((Data) envelope1.getData()).getBaseData(); validateMetricData("server", md1, "200"); - - // 2nd pre-aggregated metric - Envelope envelope2 = metrics.get(1); - validateTags(envelope2); - MetricData md2 = (MetricData) ((Data) envelope2.getData()).getBaseData(); - validateMetricData("server", md2, "200"); } private static void validateTags(Envelope envelope) { @@ -186,9 +167,9 @@ private static void validateMetricData(String type, MetricData metricData, Strin assertThat(dataPoints).hasSize(1); DataPoint dataPoint = dataPoints.get(0); assertThat(dataPoint.getCount()).isEqualTo(1); - assertThat(dataPoint.getValue()).isGreaterThan(0d).isLessThan(5 * 60 * 1000d); // (0 - 5) min - assertThat(dataPoint.getMin()).isGreaterThan(0d).isLessThan(5 * 60 * 1000d); // (0 - 5) min - assertThat(dataPoint.getMax()).isGreaterThan(0d).isLessThan(5 * 60 * 1000d); // (0 - 5) min + assertThat(dataPoint.getValue()).isGreaterThan(0d).isLessThan(60 * 1000.0); + assertThat(dataPoint.getMin()).isGreaterThan(0d).isLessThan(60 * 1000.0); + assertThat(dataPoint.getMax()).isGreaterThan(0d).isLessThan(60 * 1000.0); Map properties = metricData.getProperties(); String expectedSuccess = "200".equals(resultCode) ? "True" : "False"; if ("client".equals(type)) { diff --git a/smoke-tests/apps/HttpPreaggregatedMetrics/src/smokeTest/resources/applicationinsights.json b/smoke-tests/apps/HttpPreaggregatedMetrics/src/smokeTest/resources/applicationinsights.json deleted file mode 100644 index fabbaf6c881..00000000000 --- a/smoke-tests/apps/HttpPreaggregatedMetrics/src/smokeTest/resources/applicationinsights.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "connectionString": "InstrumentationKey=00000000-0000-0000-0000-0FEEDDADBEEF;IngestionEndpoint=http://host.docker.internal:6060/", - "role": { - "name": "testrolename", - "instance": "testroleinstance" - }, - "preview": { - "metricIntervalSeconds": 10 - } -} \ No newline at end of file diff --git a/smoke-tests/apps/InstrumentationKeyOverrides/build.gradle.kts b/smoke-tests/apps/InstrumentationKeyOverrides/build.gradle.kts index 1b1cfb106e9..eb5b96e032d 100644 --- a/smoke-tests/apps/InstrumentationKeyOverrides/build.gradle.kts +++ b/smoke-tests/apps/InstrumentationKeyOverrides/build.gradle.kts @@ -1,7 +1,3 @@ plugins { id("ai.smoke-test-war") } - -dependencies { - implementation("org.hsqldb:hsqldb:2.5.1") -} diff --git a/smoke-tests/apps/InstrumentationKeyOverrides/src/main/java/com/microsoft/applicationinsights/smoketestapp/InstrumentationKeyOverridesServlet.java b/smoke-tests/apps/InstrumentationKeyOverrides/src/main/java/com/microsoft/applicationinsights/smoketestapp/InstrumentationKeyOverridesServlet.java index 5a70c27c37d..49a45918334 100644 --- a/smoke-tests/apps/InstrumentationKeyOverrides/src/main/java/com/microsoft/applicationinsights/smoketestapp/InstrumentationKeyOverridesServlet.java +++ b/smoke-tests/apps/InstrumentationKeyOverrides/src/main/java/com/microsoft/applicationinsights/smoketestapp/InstrumentationKeyOverridesServlet.java @@ -21,108 +21,45 @@ package com.microsoft.applicationinsights.smoketestapp; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.concurrent.Callable; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.hsqldb.jdbc.JDBCDriver; @WebServlet("/*") public class InstrumentationKeyOverridesServlet extends HttpServlet { private static final Logger logger = Logger.getLogger("smoketestapp"); - public void init() throws ServletException { - try { - setupHsqldb(); - } catch (Exception e) { - // surprisingly not all application servers seem to log init exceptions to stdout - e.printStackTrace(); - throw new ServletException(e); - } - } - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException { - try { - int statusCode = doGetInternal(req); - resp.getWriter().println(statusCode); - } catch (ServletException e) { - throw e; - } catch (Exception e) { - throw new ServletException(e); - } - } + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { - private int doGetInternal(HttpServletRequest req) throws Exception { String pathInfo = req.getPathInfo(); if (pathInfo.equals("/")) { - return 200; - } else if (pathInfo.startsWith("/app")) { - Connection connection = getHsqldbConnection(); - executeStatement(connection); - connection.close(); + return; + } + if (pathInfo.startsWith("/app")) { + httpUrlConnection("https://mock.codes/200"); logger.info("hello"); - return 200; - } else { - throw new ServletException("Unexpected url: " + pathInfo); + return; } + throw new ServletException("Unexpected url: " + pathInfo); } - private static Connection getHsqldbConnection() throws Exception { - return JDBCDriver.getConnection("jdbc:hsqldb:mem:testdb", null); - } - - private void executeStatement(Connection connection) throws SQLException { - Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery("select * from abc"); - while (rs.next()) {} - rs.close(); - statement.close(); - } - - private static void setupHsqldb() throws Exception { - Connection connection = - getConnection( - new Callable() { - @Override - public Connection call() throws Exception { - return getHsqldbConnection(); - } - }); - setup(connection); - connection.close(); - } - - private static Connection getConnection(Callable callable) throws Exception { - Exception exception; - long startTimeMillis = System.currentTimeMillis(); - do { - try { - return callable.call(); - } catch (Exception e) { - exception = e; - } - } while (System.currentTimeMillis() - startTimeMillis < 30000); - throw exception; - } - - private static void setup(Connection connection) throws SQLException { - Statement statement = connection.createStatement(); - try { - statement.execute("create table abc (xyz varchar(10))"); - statement.execute("insert into abc (xyz) values ('x')"); - statement.execute("insert into abc (xyz) values ('y')"); - statement.execute("insert into abc (xyz) values ('z')"); - } finally { - statement.close(); - } + private void httpUrlConnection(String url) throws IOException { + URL obj = new URL(url); + HttpURLConnection connection = (HttpURLConnection) obj.openConnection(); + InputStream content = connection.getInputStream(); + // drain the content + byte[] buffer = new byte[1024]; + while (content.read(buffer) != -1) {} + content.close(); } } diff --git a/smoke-tests/apps/InstrumentationKeyOverrides/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/InstrumentationKeyOverridesTest.java b/smoke-tests/apps/InstrumentationKeyOverrides/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/InstrumentationKeyOverridesTest.java index b38f7f6a897..7941ae9c107 100644 --- a/smoke-tests/apps/InstrumentationKeyOverrides/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/InstrumentationKeyOverridesTest.java +++ b/smoke-tests/apps/InstrumentationKeyOverrides/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/InstrumentationKeyOverridesTest.java @@ -25,12 +25,15 @@ import static org.assertj.core.api.Assertions.assertThat; import com.microsoft.applicationinsights.smoketest.schemav2.Data; +import com.microsoft.applicationinsights.smoketest.schemav2.DataPoint; import com.microsoft.applicationinsights.smoketest.schemav2.Envelope; import com.microsoft.applicationinsights.smoketest.schemav2.MessageData; +import com.microsoft.applicationinsights.smoketest.schemav2.MetricData; import com.microsoft.applicationinsights.smoketest.schemav2.RemoteDependencyData; import com.microsoft.applicationinsights.smoketest.schemav2.RequestData; import com.microsoft.applicationinsights.smoketest.schemav2.SeverityLevel; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -81,11 +84,11 @@ private static void testApp(String iKey) throws Exception { assertThat(rd.getSuccess()).isTrue(); - assertThat(rdd.getType()).isEqualTo("SQL"); - assertThat(rdd.getTarget()).isEqualTo("hsqldb | testdb"); - assertThat(rdd.getName()).isEqualTo("SELECT testdb.abc"); - assertThat(rdd.getData()).isEqualTo("select * from abc"); - assertThat(rddEnvelope.getIKey()).isEqualTo(iKey); + assertThat(rdd.getType()).isEqualTo("Http"); + assertThat(rdd.getTarget()).isEqualTo("mock.codes"); + assertThat(rdd.getName()).isEqualTo("GET /200"); + assertThat(rdd.getData()).isEqualTo("https://mock.codes/200"); + assertThat(rdd.getResultCode()).isEqualTo("200"); assertThat(rdd.getSuccess()).isTrue(); assertThat(md.getMessage()).isEqualTo("hello"); @@ -99,5 +102,71 @@ private static void testApp(String iKey) throws Exception { rd, rdEnvelope, rddEnvelope, "GET /InstrumentationKeyOverrides/*"); SmokeTestExtension.assertParentChild( rd, rdEnvelope, mdEnvelope, "GET /InstrumentationKeyOverrides/*"); + + // and metrics too + + List clientMetrics = + testing.mockedIngestion.waitForMetricItems("http.client.duration", 1); + List serverMetrics = + testing.mockedIngestion.waitForMetricItems("http.server.duration", 1); + + verifyHttpClientPreAggregatedMetrics(clientMetrics, iKey); + verifyHttpServerPreAggregatedMetrics(serverMetrics, iKey); + } + + private static void verifyHttpClientPreAggregatedMetrics(List metrics, String iKey) { + assertThat(metrics.size()).isEqualTo(1); + + Envelope envelope1 = metrics.get(0); + assertThat(envelope1.getIKey()).isEqualTo(iKey); + validateTags(envelope1); + MetricData md1 = (MetricData) ((Data) envelope1.getData()).getBaseData(); + validateMetricData("client", md1, "200"); + } + + private static void verifyHttpServerPreAggregatedMetrics(List metrics, String iKey) { + assertThat(metrics.size()).isEqualTo(1); + + Envelope envelope1 = metrics.get(0); + assertThat(envelope1.getIKey()).isEqualTo(iKey); + validateTags(envelope1); + MetricData md1 = (MetricData) ((Data) envelope1.getData()).getBaseData(); + validateMetricData("server", md1, "200"); + } + + private static void validateTags(Envelope envelope) { + Map tags = envelope.getTags(); + assertThat(tags.get("ai.internal.sdkVersion")).isNotNull(); + assertThat(tags).containsEntry("ai.cloud.roleInstance", "testroleinstance"); + assertThat(tags).containsEntry("ai.cloud.role", "testrolename"); + } + + private static void validateMetricData(String type, MetricData metricData, String resultCode) { + List dataPoints = metricData.getMetrics(); + assertThat(dataPoints).hasSize(1); + DataPoint dataPoint = dataPoints.get(0); + assertThat(dataPoint.getCount()).isEqualTo(1); + assertThat(dataPoint.getValue()).isGreaterThan(0d).isLessThan(60 * 1000.0); + assertThat(dataPoint.getMin()).isGreaterThan(0d).isLessThan(60 * 1000.0); + assertThat(dataPoint.getMax()).isGreaterThan(0d).isLessThan(60 * 1000.0); + Map properties = metricData.getProperties(); + String expectedSuccess = "200".equals(resultCode) ? "True" : "False"; + if ("client".equals(type)) { + assertThat(properties).hasSize(9); + assertThat(properties.get("_MS.MetricId")).isEqualTo("dependencies/duration"); + assertThat(properties.get("dependency/resultCode")).isEqualTo(resultCode); + assertThat(properties.get("dependency/success")).isEqualTo(expectedSuccess); + assertThat(properties.get("dependency/target")).isEqualTo("mock.codes"); + assertThat(properties.get("dependency/type")).isEqualTo("Http"); + } else { + assertThat(properties).hasSize(7); + assertThat(properties.get("_MS.MetricId")).isEqualTo("requests/duration"); + assertThat(properties.get("request/resultCode")).isEqualTo(resultCode); + assertThat(properties.get("request/success")).isEqualTo(expectedSuccess); + } + assertThat(properties.get("operation/synthetic")).isEqualTo("False"); + assertThat(properties.get("cloud/roleInstance")).isEqualTo("testroleinstance"); + assertThat(properties.get("cloud/roleName")).isEqualTo("testrolename"); + assertThat(properties.get("_MS.IsAutocollected")).isEqualTo("True"); } } diff --git a/smoke-tests/apps/OpenTelemetryMetric/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OpenTelemetryMetricTest.java b/smoke-tests/apps/OpenTelemetryMetric/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OpenTelemetryMetricTest.java index a048e0a894a..5acdbe252a0 100644 --- a/smoke-tests/apps/OpenTelemetryMetric/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OpenTelemetryMetricTest.java +++ b/smoke-tests/apps/OpenTelemetryMetric/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/OpenTelemetryMetricTest.java @@ -40,7 +40,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -87,10 +86,7 @@ void trackLongHistogramMetric() throws Exception { private void validateHistogramMetric(String name) throws Exception { List rdList = testing.mockedIngestion.waitForItems("RequestData", 1); - List metrics = - testing.mockedIngestion.waitForItems( - SmokeTestExtension.getMetricPredicate(name), 1, 40, TimeUnit.SECONDS); - assertThat(metrics).hasSize(1); + List metrics = testing.mockedIngestion.waitForMetricItems(name, 1); Envelope rdEnvelope = rdList.get(0); RequestData rd = (RequestData) ((Data) rdEnvelope.getData()).getBaseData(); @@ -127,10 +123,7 @@ private void validateHistogramMetric(String name) throws Exception { private void validateGaugeMetric(String name) throws Exception { List rdList = testing.mockedIngestion.waitForItems("RequestData", 1); - List metrics = - testing.mockedIngestion.waitForItems( - SmokeTestExtension.getMetricPredicate(name), 1, 40, TimeUnit.SECONDS); - assertThat(metrics).hasSize(1); + List metrics = testing.mockedIngestion.waitForMetricItems(name, 1); Envelope rdEnvelope = rdList.get(0); RequestData rd = (RequestData) ((Data) rdEnvelope.getData()).getBaseData(); @@ -165,10 +158,7 @@ private void validateGaugeMetric(String name) throws Exception { private void validateCounterMetric(String name) throws Exception { List rdList = testing.mockedIngestion.waitForItems("RequestData", 1); - List metrics = - testing.mockedIngestion.waitForItems( - SmokeTestExtension.getMetricPredicate(name), 3, 40, TimeUnit.SECONDS); - assertThat(metrics).hasSize(3); + List metrics = testing.mockedIngestion.waitForMetricItems(name, 3); metrics.sort( Comparator.comparing( diff --git a/smoke-tests/apps/OpenTelemetryMetric/src/smokeTest/resources/applicationinsights.json b/smoke-tests/apps/OpenTelemetryMetric/src/smokeTest/resources/applicationinsights.json index b2771da7d9c..70d9f8e686b 100644 --- a/smoke-tests/apps/OpenTelemetryMetric/src/smokeTest/resources/applicationinsights.json +++ b/smoke-tests/apps/OpenTelemetryMetric/src/smokeTest/resources/applicationinsights.json @@ -6,9 +6,6 @@ "sampling": { "percentage": 100 }, - "preview": { - "metricIntervalSeconds": 5 - }, "customDimensions": { "tag1": "abc", "tag2": "def", diff --git a/smoke-tests/apps/RoleNameOverrides/build.gradle.kts b/smoke-tests/apps/RoleNameOverrides/build.gradle.kts index 1b1cfb106e9..eb5b96e032d 100644 --- a/smoke-tests/apps/RoleNameOverrides/build.gradle.kts +++ b/smoke-tests/apps/RoleNameOverrides/build.gradle.kts @@ -1,7 +1,3 @@ plugins { id("ai.smoke-test-war") } - -dependencies { - implementation("org.hsqldb:hsqldb:2.5.1") -} diff --git a/smoke-tests/apps/RoleNameOverrides/src/main/java/com/microsoft/applicationinsights/smoketestapp/RoleNameOverridesServlet.java b/smoke-tests/apps/RoleNameOverrides/src/main/java/com/microsoft/applicationinsights/smoketestapp/RoleNameOverridesServlet.java index db2d8786547..df42dd9f99c 100644 --- a/smoke-tests/apps/RoleNameOverrides/src/main/java/com/microsoft/applicationinsights/smoketestapp/RoleNameOverridesServlet.java +++ b/smoke-tests/apps/RoleNameOverrides/src/main/java/com/microsoft/applicationinsights/smoketestapp/RoleNameOverridesServlet.java @@ -21,108 +21,45 @@ package com.microsoft.applicationinsights.smoketestapp; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.concurrent.Callable; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.hsqldb.jdbc.JDBCDriver; @WebServlet("/*") public class RoleNameOverridesServlet extends HttpServlet { private static final Logger logger = Logger.getLogger("smoketestapp"); - public void init() throws ServletException { - try { - setupHsqldb(); - } catch (Exception e) { - // surprisingly not all application servers seem to log init exceptions to stdout - e.printStackTrace(); - throw new ServletException(e); - } - } - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException { - try { - int statusCode = doGetInternal(req); - resp.getWriter().println(statusCode); - } catch (ServletException e) { - throw e; - } catch (Exception e) { - throw new ServletException(e); - } - } + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { - private int doGetInternal(HttpServletRequest req) throws Exception { String pathInfo = req.getPathInfo(); if (pathInfo.equals("/")) { - return 200; - } else if (pathInfo.startsWith("/app")) { - Connection connection = getHsqldbConnection(); - executeStatement(connection); - connection.close(); + return; + } + if (pathInfo.startsWith("/app")) { + httpUrlConnection("https://mock.codes/200"); logger.info("hello"); - return 200; - } else { - throw new ServletException("Unexpected url: " + pathInfo); + return; } + throw new ServletException("Unexpected url: " + pathInfo); } - private static Connection getHsqldbConnection() throws Exception { - return JDBCDriver.getConnection("jdbc:hsqldb:mem:testdb", null); - } - - private void executeStatement(Connection connection) throws SQLException { - Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery("select * from abc"); - while (rs.next()) {} - rs.close(); - statement.close(); - } - - private static void setupHsqldb() throws Exception { - Connection connection = - getConnection( - new Callable() { - @Override - public Connection call() throws Exception { - return getHsqldbConnection(); - } - }); - setup(connection); - connection.close(); - } - - private static Connection getConnection(Callable callable) throws Exception { - Exception exception; - long startTimeMillis = System.currentTimeMillis(); - do { - try { - return callable.call(); - } catch (Exception e) { - exception = e; - } - } while (System.currentTimeMillis() - startTimeMillis < 30000); - throw exception; - } - - private static void setup(Connection connection) throws SQLException { - Statement statement = connection.createStatement(); - try { - statement.execute("create table abc (xyz varchar(10))"); - statement.execute("insert into abc (xyz) values ('x')"); - statement.execute("insert into abc (xyz) values ('y')"); - statement.execute("insert into abc (xyz) values ('z')"); - } finally { - statement.close(); - } + private void httpUrlConnection(String url) throws IOException { + URL obj = new URL(url); + HttpURLConnection connection = (HttpURLConnection) obj.openConnection(); + InputStream content = connection.getInputStream(); + // drain the content + byte[] buffer = new byte[1024]; + while (content.read(buffer) != -1) {} + content.close(); } } diff --git a/smoke-tests/apps/RoleNameOverrides/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/RoleNameOverridesTest.java b/smoke-tests/apps/RoleNameOverrides/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/RoleNameOverridesTest.java index 4afa9bd9d68..ce3b0226119 100644 --- a/smoke-tests/apps/RoleNameOverrides/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/RoleNameOverridesTest.java +++ b/smoke-tests/apps/RoleNameOverrides/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/RoleNameOverridesTest.java @@ -33,12 +33,15 @@ import static org.assertj.core.api.Assertions.assertThat; import com.microsoft.applicationinsights.smoketest.schemav2.Data; +import com.microsoft.applicationinsights.smoketest.schemav2.DataPoint; import com.microsoft.applicationinsights.smoketest.schemav2.Envelope; import com.microsoft.applicationinsights.smoketest.schemav2.MessageData; +import com.microsoft.applicationinsights.smoketest.schemav2.MetricData; import com.microsoft.applicationinsights.smoketest.schemav2.RemoteDependencyData; import com.microsoft.applicationinsights.smoketest.schemav2.RequestData; import com.microsoft.applicationinsights.smoketest.schemav2.SeverityLevel; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -85,10 +88,11 @@ private static void testApp(String roleName) throws Exception { assertThat(rd.getSuccess()).isTrue(); - assertThat(rdd.getType()).isEqualTo("SQL"); - assertThat(rdd.getTarget()).isEqualTo("hsqldb | testdb"); - assertThat(rdd.getName()).isEqualTo("SELECT testdb.abc"); - assertThat(rdd.getData()).isEqualTo("select * from abc"); + assertThat(rdd.getType()).isEqualTo("Http"); + assertThat(rdd.getTarget()).isEqualTo("mock.codes"); + assertThat(rdd.getName()).isEqualTo("GET /200"); + assertThat(rdd.getData()).isEqualTo("https://mock.codes/200"); + assertThat(rdd.getResultCode()).isEqualTo("200"); assertThat(rdd.getSuccess()).isTrue(); assertThat(md.getMessage()).isEqualTo("hello"); @@ -100,6 +104,73 @@ private static void testApp(String roleName) throws Exception { SmokeTestExtension.assertParentChild(rd, rdEnvelope, rddEnvelope, "GET /RoleNameOverrides/*"); SmokeTestExtension.assertParentChild(rd, rdEnvelope, mdEnvelope, "GET /RoleNameOverrides/*"); + + // and metrics too + + List clientMetrics = + testing.mockedIngestion.waitForMetricItems("http.client.duration", 1); + List serverMetrics = + testing.mockedIngestion.waitForMetricItems("http.server.duration", 1); + + verifyHttpClientPreAggregatedMetrics(clientMetrics, roleName); + verifyHttpServerPreAggregatedMetrics(serverMetrics, roleName); + } + + private static void verifyHttpClientPreAggregatedMetrics( + List metrics, String roleName) { + assertThat(metrics.size()).isEqualTo(1); + + Envelope envelope1 = metrics.get(0); + validateTags(envelope1, roleName); + MetricData md1 = (MetricData) ((Data) envelope1.getData()).getBaseData(); + validateMetricData("client", md1, "200", roleName); + } + + private static void verifyHttpServerPreAggregatedMetrics( + List metrics, String roleName) { + assertThat(metrics.size()).isEqualTo(1); + + Envelope envelope1 = metrics.get(0); + validateTags(envelope1, roleName); + MetricData md1 = (MetricData) ((Data) envelope1.getData()).getBaseData(); + validateMetricData("server", md1, "200", roleName); + } + + private static void validateTags(Envelope envelope, String roleName) { + Map tags = envelope.getTags(); + assertThat(tags.get("ai.internal.sdkVersion")).isNotNull(); + assertThat(tags).containsEntry("ai.cloud.roleInstance", "testroleinstance"); + assertThat(tags).containsEntry("ai.cloud.role", roleName); + } + + private static void validateMetricData( + String type, MetricData metricData, String resultCode, String roleName) { + List dataPoints = metricData.getMetrics(); + assertThat(dataPoints).hasSize(1); + DataPoint dataPoint = dataPoints.get(0); + assertThat(dataPoint.getCount()).isEqualTo(1); + assertThat(dataPoint.getValue()).isGreaterThan(0d).isLessThan(60 * 1000.0); + assertThat(dataPoint.getMin()).isGreaterThan(0d).isLessThan(60 * 1000.0); + assertThat(dataPoint.getMax()).isGreaterThan(0d).isLessThan(60 * 1000.0); + Map properties = metricData.getProperties(); + String expectedSuccess = "200".equals(resultCode) ? "True" : "False"; + if ("client".equals(type)) { + assertThat(properties).hasSize(9); + assertThat(properties.get("_MS.MetricId")).isEqualTo("dependencies/duration"); + assertThat(properties.get("dependency/resultCode")).isEqualTo(resultCode); + assertThat(properties.get("dependency/success")).isEqualTo(expectedSuccess); + assertThat(properties.get("dependency/target")).isEqualTo("mock.codes"); + assertThat(properties.get("dependency/type")).isEqualTo("Http"); + } else { + assertThat(properties).hasSize(7); + assertThat(properties.get("_MS.MetricId")).isEqualTo("requests/duration"); + assertThat(properties.get("request/resultCode")).isEqualTo(resultCode); + assertThat(properties.get("request/success")).isEqualTo(expectedSuccess); + } + assertThat(properties.get("operation/synthetic")).isEqualTo("False"); + assertThat(properties.get("cloud/roleInstance")).isEqualTo("testroleinstance"); + assertThat(properties.get("cloud/roleName")).isEqualTo(roleName); + assertThat(properties.get("_MS.IsAutocollected")).isEqualTo("True"); } @Environment(TOMCAT_8_JAVA_8) diff --git a/smoke-tests/apps/Statsbeat/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/StatsbeatTest.java b/smoke-tests/apps/Statsbeat/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/StatsbeatTest.java index 75114c4b9a1..6ba67f72f03 100644 --- a/smoke-tests/apps/Statsbeat/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/StatsbeatTest.java +++ b/smoke-tests/apps/Statsbeat/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/StatsbeatTest.java @@ -49,8 +49,7 @@ abstract class StatsbeatTest { @TargetUri(value = "/index.jsp") void testStatsbeat() throws Exception { List metrics = - testing.mockedIngestion.waitForItems( - SmokeTestExtension.getMetricPredicate("Feature"), 2, 70, TimeUnit.SECONDS); + testing.mockedIngestion.waitForMetricItems("Feature", 2, 70, TimeUnit.SECONDS); MetricData data = (MetricData) ((Data) metrics.get(0).getData()).getBaseData(); assertCommon(data); @@ -68,8 +67,7 @@ void testStatsbeat() throws Exception { assertThat(instrumentationData.getProperties()).hasSize(9); List attachMetrics = - testing.mockedIngestion.waitForItems( - SmokeTestExtension.getMetricPredicate("Attach"), 1, 70, TimeUnit.SECONDS); + testing.mockedIngestion.waitForMetricItems("Attach", 1, 70, TimeUnit.SECONDS); MetricData attachData = (MetricData) ((Data) attachMetrics.get(0).getData()).getBaseData(); assertCommon(attachData); @@ -77,11 +75,8 @@ void testStatsbeat() throws Exception { assertThat(attachData.getProperties()).hasSize(8); List requestSuccessCountMetrics = - testing.mockedIngestion.waitForItems( - SmokeTestExtension.getMetricPredicate("Request Success Count"), - 1, - 70, - TimeUnit.SECONDS); + testing.mockedIngestion.waitForMetricItems( + "Request Success Count", 1, 70, TimeUnit.SECONDS); MetricData requestSuccessCountData = (MetricData) ((Data) requestSuccessCountMetrics.get(0).getData()).getBaseData(); @@ -91,8 +86,7 @@ void testStatsbeat() throws Exception { assertThat(requestSuccessCountData.getProperties()).hasSize(9); List requestDurationMetrics = - testing.mockedIngestion.waitForItems( - SmokeTestExtension.getMetricPredicate("Request Duration"), 1, 70, TimeUnit.SECONDS); + testing.mockedIngestion.waitForMetricItems("Request Duration", 1, 70, TimeUnit.SECONDS); MetricData requestDurationData = (MetricData) ((Data) requestDurationMetrics.get(0).getData()).getBaseData(); diff --git a/smoke-tests/apps/SystemExit/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/SystemExitTest.java b/smoke-tests/apps/SystemExit/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/SystemExitTest.java index dc24c468096..13335bc4dad 100644 --- a/smoke-tests/apps/SystemExit/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/SystemExitTest.java +++ b/smoke-tests/apps/SystemExit/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/SystemExitTest.java @@ -57,8 +57,7 @@ void doDelayedSystemExitTest() throws Exception { }, 10, TimeUnit.SECONDS); - testing.mockedIngestion.waitForItem( - SmokeTestExtension.getMetricPredicate("counter"), 10, TimeUnit.SECONDS); + testing.mockedIngestion.waitForMetricItems("counter", 1); } @Environment(JAVA_8) diff --git a/smoke-tests/apps/gRPC/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/GrpcTest.java b/smoke-tests/apps/gRPC/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/GrpcTest.java index 947cebf3124..e6e48d08d83 100644 --- a/smoke-tests/apps/gRPC/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/GrpcTest.java +++ b/smoke-tests/apps/gRPC/src/smokeTest/java/com/microsoft/applicationinsights/smoketest/GrpcTest.java @@ -41,7 +41,6 @@ import com.microsoft.applicationinsights.smoketest.schemav2.RequestData; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -55,11 +54,9 @@ abstract class GrpcTest { void doSimpleTest() throws Exception { List rdList = testing.mockedIngestion.waitForItems("RequestData", 2); List rpcClientDurationMetrics = - testing.mockedIngestion.waitForItems( - SmokeTestExtension.getMetricPredicate("rpc.client.duration"), 2, 40, TimeUnit.SECONDS); + testing.mockedIngestion.waitForMetricItems("rpc.client.duration", 1); List rpcServerMetrics = - testing.mockedIngestion.waitForItems( - SmokeTestExtension.getMetricPredicate("rpc.server.duration"), 2, 40, TimeUnit.SECONDS); + testing.mockedIngestion.waitForMetricItems("rpc.server.duration", 1); Envelope rdEnvelope1 = getRequestEnvelope(rdList, "GET /simple"); Envelope rdEnvelope2 = getRequestEnvelope(rdList, "example.Greeter/SayHello"); @@ -104,6 +101,10 @@ void doSimpleTest() throws Exception { @TargetUri("/conversation") void doConversationTest() throws Exception { List rdList = testing.mockedIngestion.waitForItems("RequestData", 2); + List rpcClientDurationMetrics = + testing.mockedIngestion.waitForMetricItems("rpc.client.duration", 1); + List rpcServerMetrics = + testing.mockedIngestion.waitForMetricItems("rpc.server.duration", 1); Envelope rdEnvelope1 = getRequestEnvelope(rdList, "GET /conversation"); Envelope rdEnvelope2 = getRequestEnvelope(rdList, "example.Greeter/Conversation"); @@ -144,6 +145,9 @@ void doConversationTest() throws Exception { "GET /conversation", "example.Greeter/Conversation", false); + + verifyRpcClientDurationPreAggregatedMetrics(rpcClientDurationMetrics); + verifyRpcServerDurationPreAggregatedMetrics(rpcServerMetrics); } private static Envelope getRequestEnvelope(List envelopes, String name) { @@ -168,34 +172,21 @@ private static Envelope getDependencyEnvelope(List envelopes, String n } private static void verifyRpcClientDurationPreAggregatedMetrics(List metrics) { - assertThat(metrics.size()).isEqualTo(2); + assertThat(metrics.size()).isEqualTo(1); - // 1st pre-aggregated metric Envelope envelope1 = metrics.get(0); validateTags(envelope1); MetricData md1 = (MetricData) ((Data) envelope1.getData()).getBaseData(); validateMetricData("client", md1); - - // 2nd pre-aggregated metric - Envelope envelope2 = metrics.get(1); - validateTags(envelope2); - MetricData md2 = (MetricData) ((Data) envelope2.getData()).getBaseData(); - validateMetricData("client", md2); } private static void verifyRpcServerDurationPreAggregatedMetrics(List metrics) { - assertThat(metrics.size()).isEqualTo(2); - // 1st pre-aggregated metric + assertThat(metrics.size()).isEqualTo(1); + Envelope envelope1 = metrics.get(0); validateTags(envelope1); MetricData md1 = (MetricData) ((Data) envelope1.getData()).getBaseData(); validateMetricData("server", md1); - - // 2nd pre-aggregated metric - Envelope envelope2 = metrics.get(1); - validateTags(envelope2); - MetricData md2 = (MetricData) ((Data) envelope2.getData()).getBaseData(); - validateMetricData("server", md2); } private static void validateTags(Envelope envelope) { @@ -210,9 +201,9 @@ private static void validateMetricData(String type, MetricData metricData) { assertThat(dataPoints).hasSize(1); DataPoint dataPoint = dataPoints.get(0); assertThat(dataPoint.getCount()).isEqualTo(1); - assertThat(dataPoint.getValue()).isGreaterThan(0d).isLessThan(5 * 60 * 1000d); // (0 - 5) min - assertThat(dataPoint.getMin()).isGreaterThan(0d).isLessThan(5 * 60 * 1000d); // (0 - 5) min - assertThat(dataPoint.getMin()).isGreaterThan(0d).isLessThan(5 * 60 * 1000d); // (0 - 5) min + assertThat(dataPoint.getValue()).isGreaterThan(0d).isLessThan(60 * 1000.0); + assertThat(dataPoint.getMin()).isGreaterThan(0d).isLessThan(60 * 1000.0); + assertThat(dataPoint.getMin()).isGreaterThan(0d).isLessThan(60 * 1000.0); Map properties = metricData.getProperties(); if ("client".equals(type)) { assertThat(properties).hasSize(8); diff --git a/smoke-tests/apps/gRPC/src/smokeTest/resources/applicationinsights.json b/smoke-tests/apps/gRPC/src/smokeTest/resources/applicationinsights.json deleted file mode 100644 index df270eac2f8..00000000000 --- a/smoke-tests/apps/gRPC/src/smokeTest/resources/applicationinsights.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "role": { - "name": "testrolename", - "instance": "testroleinstance" - }, - "sampling": { - "percentage": 100 - }, - "preview": { - "metricIntervalSeconds": 10 - } -} diff --git a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java index de1a16557ec..f39ff0ec8a4 100644 --- a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java +++ b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/SmokeTestExtension.java @@ -111,7 +111,7 @@ public class SmokeTestExtension private final boolean usesGlobalIngestionEndpoint; public static SmokeTestExtension create() { - return new SmokeTestExtension(null, null, false, false, false); + return builder().build(); } public static SmokeTestExtensionBuilder builder() { @@ -251,6 +251,17 @@ private void waitForHealthCheckTelemetry(String contextRootUrl) }, TELEMETRY_RECEIVE_TIMEOUT_SECONDS, TimeUnit.SECONDS); + mockedIngestion.waitForItem( + input -> { + if (!"MetricData".equals(input.getData().getBaseType())) { + return false; + } + MetricData data = (MetricData) ((Data) input.getData()).getBaseData(); + String metricId = data.getProperties().get("_MS.MetricId"); + return metricId != null && metricId.equals("requests/duration"); + }, + 10, // metrics should come in pretty quickly after spans + TimeUnit.SECONDS); System.out.printf( "Received request telemetry after %.3f seconds...%n", receivedTelemetryTimer.elapsed(TimeUnit.MILLISECONDS) / 1000.0); @@ -374,6 +385,7 @@ protected Set getLivenessCheckPorts() { List javaToolOptions = new ArrayList<>(); javaToolOptions.add("-Dapplicationinsights.testing.batch-schedule-delay-millis=500"); + javaToolOptions.add("-Dapplicationinsights.testing.metric-reader-interval-millis=500"); if (usesGlobalIngestionEndpoint) { javaToolOptions.add( "-Dapplicationinsights.testing.global-ingestion-endpoint=" + FAKE_INGESTION_ENDPOINT); diff --git a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/fakeingestion/MockedAppInsightsIngestionServer.java b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/fakeingestion/MockedAppInsightsIngestionServer.java index cbbc638e121..29058ee0c8d 100644 --- a/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/fakeingestion/MockedAppInsightsIngestionServer.java +++ b/smoke-tests/framework/src/main/java/com/microsoft/applicationinsights/smoketest/fakeingestion/MockedAppInsightsIngestionServer.java @@ -21,6 +21,7 @@ package com.microsoft.applicationinsights.smoketest.fakeingestion; +import com.microsoft.applicationinsights.smoketest.SmokeTestExtension; import com.microsoft.applicationinsights.smoketest.schemav2.Data; import com.microsoft.applicationinsights.smoketest.schemav2.Domain; import com.microsoft.applicationinsights.smoketest.schemav2.Envelope; @@ -209,6 +210,18 @@ public List waitForItems( return servlet.waitForItems(condition, numItems, timeout, timeUnit); } + public List waitForMetricItems(String metricName, int numItems) + throws InterruptedException, TimeoutException { + return waitForMetricItems(metricName, numItems, 10, TimeUnit.SECONDS); + } + + public List waitForMetricItems( + String metricName, int numItems, int timeout, TimeUnit timeUnit) + throws InterruptedException, TimeoutException { + return servlet.waitForItems( + SmokeTestExtension.getMetricPredicate(metricName), numItems, timeout, timeUnit); + } + // this is important for Message and Exception types which can also be captured outside of // requests public List waitForItemsInOperation(String type, int numItems, String operationId)