Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenTelemetry Pre-aggregated metrics #2439

Merged
merged 103 commits into from
Aug 26, 2022
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
f0959e6
Add placeholder
heyams Aug 5, 2022
e707111
Rename
heyams Aug 5, 2022
c4e5474
Start mapping pre-agg metrics to azure monitor metrics
heyams Aug 8, 2022
fd58580
Add preagg metrics extractor
heyams Aug 9, 2022
b47c27c
Fix spotless
heyams Aug 9, 2022
4fcda82
Merge branch 'main' into heya/pre-aggregated-metrics
heyams Aug 9, 2022
341b432
Use stdout
heyams Aug 9, 2022
684b7ea
Update test and refactor
heyams Aug 10, 2022
a3b8fdd
Fix test
heyams Aug 10, 2022
e314498
Fix lgtm
heyams Aug 10, 2022
58e50bd
Perf buckets
trask Aug 10, 2022
d37d258
Merge branch 'perfbuckets' into heya/pre-aggregated-metrics
heyams Aug 11, 2022
d448696
Hack httpservermetric onEnd
heyams Aug 11, 2022
308b5e1
Fix spotless
heyams Aug 11, 2022
0be0aa6
Shade HttpClientMetrics
heyams Aug 11, 2022
17794bf
Fix conversion
heyams Aug 11, 2022
18156e6
Disable performanceBucket in unit test
heyams Aug 11, 2022
b38b24f
Validate pre-agg in smoke test
heyams Aug 11, 2022
ecb893c
Remove debug logs
heyams Aug 11, 2022
3901f3d
Fix typo
heyams Aug 11, 2022
659c2b2
Remove commented out code
heyams Aug 11, 2022
02a4440
Fix rpc
heyams Aug 11, 2022
e7849db
Fix ci tests
heyams Aug 11, 2022
fa44655
Refactor
heyams Aug 12, 2022
c028ac8
Add a new smoke test
heyams Aug 12, 2022
07b42e0
Clean up smoke test
heyams Aug 12, 2022
6defb03
Add perfromanceBucket to http.client.duration and http.server.duratio…
heyams Aug 12, 2022
4655620
Revert httpclient smoke test
heyams Aug 12, 2022
8d1abbd
Fix preaggregated metrics smoke test
heyams Aug 12, 2022
d0b9c7d
Delete a todo that has been resolved
heyams Aug 12, 2022
93474a0
Delete stdout
heyams Aug 12, 2022
8f667aa
Delete unused dependencies and ivar
heyams Aug 12, 2022
5a598ca
Verify rpc pre-aggregated metrics
heyams Aug 12, 2022
cb2f9c6
Generate rpcClientMetrics to check its metric content
heyams Aug 12, 2022
ec5616a
Fix NPE and fix perfBucket is not part of rpc metrics attributes
heyams Aug 12, 2022
d7f9d05
Merge branch 'main' into heya/pre-aggregated-metrics
heyams Aug 15, 2022
8936114
Merge branch 'main' into heya/pre-aggregated-metrics
heyams Aug 15, 2022
ea60a0a
Send rpc/http.client as dependencies/duration and rpc/http.server as …
heyams Aug 15, 2022
354c24e
Stdout http.server.duration metric
heyams Aug 15, 2022
73ac883
Add target to httpClientMetrics' attributes
heyams Aug 15, 2022
650f81c
Add taret to rpcClientMetrics
heyams Aug 16, 2022
aad7c54
Add target to rpcclientmetrics
heyams Aug 16, 2022
54faf3e
Fix smoke test
heyams Aug 16, 2022
1be4807
Fix rpc smoke test
heyams Aug 16, 2022
5251d59
Remove stdout suppressWarning
heyams Aug 16, 2022
ba69e1c
Debug 'ai.user.userAgent'
heyams Aug 17, 2022
3e2bdb8
Remove perf bucket and add isSynthetic
heyams Aug 18, 2022
c8d4f0c
Address comments
heyams Aug 18, 2022
a0270bf
Fix compilation errors
heyams Aug 18, 2022
9185a53
Remove unused imports
heyams Aug 18, 2022
769a1e7
Add MS_ProcessedByMetricExtractors to request/dependency not pre-agg …
heyams Aug 18, 2022
346179c
Remove an assert
heyams Aug 18, 2022
9c223f6
Rename a method
heyams Aug 18, 2022
4fcee8a
Fix tests
heyams Aug 18, 2022
f6974b0
Fix test
heyams Aug 18, 2022
872d821
Fix ci tests
heyams Aug 18, 2022
0e3a638
Fix spotless
heyams Aug 19, 2022
a6f2e99
Reimplement the logic for adding _MS.ProcessedByMetricExtractors
heyams Aug 19, 2022
50d7984
Remove isPreAggregated attribute
heyams Aug 22, 2022
8c4ce32
Fix spotless
heyams Aug 22, 2022
f41348b
Merge branch 'main' into heya/pre-aggregated-metrics
heyams Aug 22, 2022
278b526
Address comments
heyams Aug 22, 2022
b84e94b
Fix smoke tests
heyams Aug 22, 2022
f63b8f5
Fix smoke tests
heyams Aug 22, 2022
2fef285
Fix tests
heyams Aug 22, 2022
bd12ff8
Fix tests
heyams Aug 22, 2022
e0a6763
Fix
heyams Aug 22, 2022
f06077e
Debug synthetic
heyams Aug 22, 2022
b9d6669
Fix tests
heyams Aug 22, 2022
7802806
Fix userAgent otel key and more tests
heyams Aug 22, 2022
6075e3f
Fix more tests
heyams Aug 22, 2022
25384f3
Fix more tests
heyams Aug 23, 2022
cd38814
Fix one more test
heyams Aug 23, 2022
2d45b25
Fix pre-agg metrics' custom dimensions
heyams Aug 23, 2022
9cac0e8
Revert change and add a todo
heyams Aug 23, 2022
2f8e95f
Fix pre-agg metrics were failing to be ingested into mdm
heyams Aug 23, 2022
29459ea
Fix tests
heyams Aug 23, 2022
120fa3a
Remove a todo
heyams Aug 23, 2022
8978dec
Remove debug logs
heyams Aug 23, 2022
9890725
Merge branch 'main' into heya/pre-aggregated-metrics
heyams Aug 23, 2022
79b0a08
Address comments
heyams Aug 23, 2022
ab331d7
Add appinsights code comment
heyams Aug 23, 2022
8ab3154
Exclude return statement
heyams Aug 23, 2022
e216ed4
Use containsExactly
heyams Aug 23, 2022
68bf945
Verify _MS.ProcessedByMetricExtractors
heyams Aug 24, 2022
111c773
Fix wrong import
heyams Aug 24, 2022
998e042
Fix a compilation error
heyams Aug 24, 2022
24ad4c7
Turn baseextractor into a util class
heyams Aug 24, 2022
6d46468
Address comments
heyams Aug 24, 2022
12878c8
Merge branch 'main' into heya/pre-aggregated-metrics
heyams Aug 24, 2022
ad88fc4
Fix tests
heyams Aug 24, 2022
22cdd01
Address more comments
heyams Aug 24, 2022
b916cbf
Fix tests
heyams Aug 24, 2022
a1ee8e3
Use attrbuteKey constants
heyams Aug 24, 2022
8b9601f
Address comments
heyams Aug 25, 2022
a77fb40
Merge branch 'main' into heya/pre-aggregated-metrics
heyams Aug 25, 2022
81efed1
Fix a typo
heyams Aug 25, 2022
d61b7d9
Fix one more test
heyams Aug 25, 2022
1855abd
Remove MetricView and turn extractors into util class
heyams Aug 25, 2022
53f145a
Apply the same to rpcservermetrics
heyams Aug 25, 2022
0db361d
Rename
heyams Aug 25, 2022
1e7fb05
Comments
heyams Aug 25, 2022
9f77259
Use 'Http' for dependency type
heyams Aug 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions agent/agent-bootstrap/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ dependencies {
// needed to access io.opentelemetry.instrumentation.api.aisdk.MicrometerUtil
// TODO (heya) remove this when updating to upstream micrometer instrumentation
compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api")
compileOnly("io.opentelemetry:opentelemetry-semconv")

compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value")

implementation("ch.qos.logback:logback-classic")
implementation("ch.qos.logback.contrib:logback-json-classic")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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 io.opentelemetry.instrumentation.api.instrumenter;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import javax.annotation.Nullable;

public final class Utils {
heyams marked this conversation as resolved.
Show resolved Hide resolved

public static final String IS_SYNTHETIC = "isSynthetic";
public static final String TARGET = "target";
public static final String IS_PRE_AGGREGATED = "isPreAggregated";

@SuppressWarnings("SystemOut")
public static boolean isUserAgentBot(Attributes endAttributes, Attributes startAttributes) {
String aiUserAgent =
getAttribute(AttributeKey.stringKey("ai.user.userAgent"), endAttributes, startAttributes);
// TODO to be removed debug log
System.out.println("############## HttpServerMetrics::aiUserAgent: " + aiUserAgent);
if (aiUserAgent != null && aiUserAgent.indexOf("AlwaysOn") >= 0) {
return true;
}
return false;
}

@Nullable
private static <T> T getAttribute(AttributeKey<T> key, Attributes... attributesList) {
for (Attributes attributes : attributesList) {
T value = attributes.get(key);
if (value != null) {
return value;
}
}
return null;
}

private Utils() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
/*
* 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 io.opentelemetry.instrumentation.api.instrumenter.http;

import static io.opentelemetry.instrumentation.api.instrumenter.Utils.IS_PRE_AGGREGATED;
import static io.opentelemetry.instrumentation.api.instrumenter.Utils.IS_SYNTHETIC;
import static io.opentelemetry.instrumentation.api.instrumenter.Utils.TARGET;
import static io.opentelemetry.instrumentation.api.instrumenter.Utils.isUserAgentBot;
import static io.opentelemetry.instrumentation.api.instrumenter.http.TemporaryMetricsView.applyClientDurationAndSizeView;
import static java.util.logging.Level.FINE;

import com.google.auto.value.AutoValue;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
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.semconv.trace.attributes.SemanticAttributes;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.annotation.Nullable;

/**
* {@link OperationListener} which keeps track of <a
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-client">HTTP
* client metrics</a>.
*/
public final class HttpClientMetrics implements OperationListener {
heyams marked this conversation as resolved.
Show resolved Hide resolved

private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1);

private static final ContextKey<State> HTTP_CLIENT_REQUEST_METRICS_STATE =
ContextKey.named("http-client-request-metrics-state");

private static final Logger logger = Logger.getLogger(HttpClientMetrics.class.getName());

/**
* Returns a {@link OperationMetrics} which can be used to enable recording of {@link
* HttpClientMetrics} on an {@link
* io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder}.
*/
public static OperationMetrics get() {
return HttpClientMetrics::new;
}

private final DoubleHistogram duration;
private final LongHistogram requestSize;
private final LongHistogram responseSize;

private HttpClientMetrics(Meter meter) {
duration =
meter
.histogramBuilder("http.client.duration")
.setUnit("ms")
.setDescription("The duration of the outbound HTTP request")
.build();
requestSize =
meter
.histogramBuilder("http.client.request.size")
.setUnit("By")
.setDescription("The size of HTTP request messages")
.ofLongs()
.build();
responseSize =
meter
.histogramBuilder("http.client.response.size")
.setUnit("By")
.setDescription("The size of HTTP response messages")
.ofLongs()
.build();
}

@Override
public Context onStart(Context context, Attributes startAttributes, long startNanos) {
return context.with(
HTTP_CLIENT_REQUEST_METRICS_STATE,
new AutoValue_HttpClientMetrics_State(startAttributes, startNanos));
}

@Override
public void onEnd(Context context, Attributes endAttributes, long endNanos) {
State state = context.get(HTTP_CLIENT_REQUEST_METRICS_STATE);
if (state == null) {
logger.log(
FINE,
"No state present when ending context {0}. Cannot record HTTP request metrics.",
context);
return;
}
Attributes durationAndSizeAttributes =
applyClientDurationAndSizeView(state.startAttributes(), endAttributes);

// START APPLICATION INSIGHTS CODE

// 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,
String.valueOf(isUserAgentBot(endAttributes, state.startAttributes())))
heyams marked this conversation as resolved.
Show resolved Hide resolved
.put(TARGET, getTargetForHttpClientSpan(durationAndSizeAttributes))
.build();
// END APPLICATION INSIGHTS CODE
heyams marked this conversation as resolved.
Show resolved Hide resolved

this.duration.record(
(endNanos - state.startTimeNanos()) / NANOS_PER_MS, durationAttributes, context);

Long requestLength =
getAttribute(
SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, endAttributes, state.startAttributes());
if (requestLength != null) {
requestSize.record(requestLength, durationAndSizeAttributes);
}
Long responseLength =
getAttribute(
SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH,
endAttributes,
state.startAttributes());
if (responseLength != null) {
responseSize.record(responseLength, durationAndSizeAttributes);
}
}

private static String getTargetForHttpClientSpan(Attributes attributes) {
// from the spec, at least one of the following sets of attributes is required:
// * http.url
// * http.scheme, http.host, http.target
// * http.scheme, net.peer.name, net.peer.port, http.target
// * http.scheme, net.peer.ip, net.peer.port, http.target
String target = getTargetFromPeerService(attributes);
if (target != null) {
return target;
}
// note http.host includes the port (at least when non-default)
target = attributes.get(SemanticAttributes.HTTP_HOST);
if (target != null) {
String scheme = attributes.get(SemanticAttributes.HTTP_SCHEME);
if ("http".equals(scheme)) {
if (target.endsWith(":80")) {
target = target.substring(0, target.length() - 3);
heyams marked this conversation as resolved.
Show resolved Hide resolved
}
} else if ("https".equals(scheme)) {
if (target.endsWith(":443")) {
target = target.substring(0, target.length() - 4);
heyams marked this conversation as resolved.
Show resolved Hide resolved
}
}
return target;
}
String url = attributes.get(SemanticAttributes.HTTP_URL);
if (url != null) {
target = getTargetFromUrl(url);
if (target != null) {
return target;
}
}
String scheme = attributes.get(SemanticAttributes.HTTP_SCHEME);
int defaultPort;
if ("http".equals(scheme)) {
defaultPort = 80;
} else if ("https".equals(scheme)) {
defaultPort = 443;
} else {
defaultPort = 0;
}
target = getTargetFromNetAttributes(attributes, defaultPort);
if (target != null) {
return target;
}
// this should not happen, just a failsafe
return "Http";
}

@Nullable
private static String getTargetFromPeerService(Attributes attributes) {
// do not append port to peer.service
return attributes.get(SemanticAttributes.PEER_SERVICE);
}

@Nullable
private static String getTargetFromNetAttributes(Attributes attributes, int defaultPort) {
String target = getHostFromNetAttributes(attributes);
if (target == null) {
return null;
}
// append net.peer.port to target
Long port = attributes.get(SemanticAttributes.NET_PEER_PORT);
if (port != null && port != defaultPort) {
return target + ":" + port;
}
return target;
}

@Nullable
private static String getHostFromNetAttributes(Attributes attributes) {
String host = attributes.get(SemanticAttributes.NET_PEER_NAME);
if (host != null) {
return host;
}
return attributes.get(SemanticAttributes.NET_PEER_IP);
}

@Nullable
private static String getTargetFromUrl(String url) {
int schemeEndIndex = url.indexOf(':');
if (schemeEndIndex == -1) {
heyams marked this conversation as resolved.
Show resolved Hide resolved
// not a valid url
return null;
}

int len = url.length();
if (schemeEndIndex + 2 < len
heyams marked this conversation as resolved.
Show resolved Hide resolved
&& url.charAt(schemeEndIndex + 1) == '/'
&& url.charAt(schemeEndIndex + 2) == '/') {
// has authority component
// look for
// '/' - start of path
// '?' or end of string - empty path
int index;
for (index = schemeEndIndex + 3; index < len; index++) {
char c = url.charAt(index);
if (c == '/' || c == '?' || c == '#') {
break;
}
}
String target = url.substring(schemeEndIndex + 3, index);
return target.isEmpty() ? null : target;
} else {
// has no authority
return null;
}
}

@Nullable
private static <T> T getAttribute(AttributeKey<T> key, Attributes... attributesList) {
for (Attributes attributes : attributesList) {
T value = attributes.get(key);
if (value != null) {
return value;
}
}
return null;
}

@AutoValue
abstract static class State {

abstract Attributes startAttributes();

abstract long startTimeNanos();
}
}
Loading