Skip to content

Commit 2259d46

Browse files
authored
Support item count and operation name in azmonexporter (#2443)
* Support item count and operation name in azmonexporter * Introduce AiSemanticAttributes * Fix * Comment * Fix * Fix * Spotless
1 parent a3b035d commit 2259d46

30 files changed

+832
-711
lines changed

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/classicsdk/BytecodeUtilImpl.java

Lines changed: 49 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
import static java.util.concurrent.TimeUnit.MILLISECONDS;
2525
import static java.util.concurrent.TimeUnit.SECONDS;
2626

27-
import com.azure.monitor.opentelemetry.exporter.implementation.AiOperationNameSpanProcessor;
27+
import com.azure.monitor.opentelemetry.exporter.implementation.AiSemanticAttributes;
28+
import com.azure.monitor.opentelemetry.exporter.implementation.OperationNames;
29+
import com.azure.monitor.opentelemetry.exporter.implementation.SamplingScoreGeneratorV2;
2830
import com.azure.monitor.opentelemetry.exporter.implementation.builders.AbstractTelemetryBuilder;
2931
import com.azure.monitor.opentelemetry.exporter.implementation.builders.AvailabilityTelemetryBuilder;
3032
import com.azure.monitor.opentelemetry.exporter.implementation.builders.EventTelemetryBuilder;
@@ -40,16 +42,12 @@
4042
import com.azure.monitor.opentelemetry.exporter.implementation.utils.FormattedDuration;
4143
import com.azure.monitor.opentelemetry.exporter.implementation.utils.FormattedTime;
4244
import com.azure.monitor.opentelemetry.exporter.implementation.utils.Strings;
43-
import com.azure.monitor.opentelemetry.exporter.implementation.utils.TelemetryUtil;
4445
import com.microsoft.applicationinsights.agent.bootstrap.BytecodeUtil.BytecodeUtilDelegate;
4546
import com.microsoft.applicationinsights.agent.internal.legacyheaders.AiLegacyPropagator;
46-
import com.microsoft.applicationinsights.agent.internal.sampling.SamplingScoreGeneratorV2;
4747
import com.microsoft.applicationinsights.agent.internal.statsbeat.FeatureStatsbeat;
4848
import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryClient;
4949
import io.opentelemetry.api.trace.Span;
5050
import io.opentelemetry.api.trace.SpanContext;
51-
import io.opentelemetry.context.Context;
52-
import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan;
5351
import io.opentelemetry.sdk.trace.ReadableSpan;
5452
import java.net.URI;
5553
import java.net.URL;
@@ -472,78 +470,37 @@ public void logErrorOnce(Throwable t) {
472470
private static void track(
473471
AbstractTelemetryBuilder telemetryBuilder, Map<String, String> tags, boolean applySampling) {
474472

475-
String operationId = tags.get(ContextTagKeys.AI_OPERATION_ID.toString());
473+
String existingOperationId = tags.get(ContextTagKeys.AI_OPERATION_ID.toString());
476474

477-
SpanContext context = Span.current().getSpanContext();
478-
if (context.isValid()) {
479-
String operationParentId = tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID.toString());
480-
String operationName = tags.get(ContextTagKeys.AI_OPERATION_NAME.toString());
475+
Span span = Span.current();
476+
SpanContext spanContext = span.getSpanContext();
481477

482-
trackInsideValidSpanContext(
483-
telemetryBuilder, operationId, operationParentId, operationName, context, applySampling);
484-
} else {
485-
trackAsStandalone(telemetryBuilder, operationId, applySampling);
486-
}
478+
boolean isPartOfTheCurrentTrace =
479+
spanContext.isValid()
480+
&& (existingOperationId == null
481+
|| existingOperationId.equals(spanContext.getTraceId()));
487482

488-
if (featureStatsbeat != null) {
489-
featureStatsbeat.track2xBridgeUsage();
490-
}
491-
}
492-
493-
private static void trackInsideValidSpanContext(
494-
AbstractTelemetryBuilder telemetryBuilder,
495-
@Nullable String operationId,
496-
@Nullable String operationParentId,
497-
@Nullable String operationName,
498-
SpanContext spanContext,
499-
boolean applySampling) {
500-
501-
if (operationId != null && !operationId.equals(spanContext.getTraceId())) {
502-
trackAsStandalone(telemetryBuilder, operationId, applySampling);
483+
if (isPartOfTheCurrentTrace && applySampling && !spanContext.isSampled()) {
484+
// no need to do anything more, sampled out
503485
return;
504486
}
505487

506-
if (!spanContext.isSampled()) {
507-
// sampled out
508-
return;
488+
if (isPartOfTheCurrentTrace) {
489+
setOperationTagsFromTheCurrentSpan(
490+
telemetryBuilder, tags, existingOperationId, spanContext, span);
509491
}
510492

511-
telemetryBuilder.addTag(ContextTagKeys.AI_OPERATION_ID.toString(), spanContext.getTraceId());
512-
513-
if (operationParentId == null) {
514-
telemetryBuilder.addTag(
515-
ContextTagKeys.AI_OPERATION_PARENT_ID.toString(), spanContext.getSpanId());
516-
}
517-
518-
if (operationName == null) {
519-
Span localRootSpan = LocalRootSpan.fromContextOrNull(Context.current());
520-
if (localRootSpan instanceof ReadableSpan) {
521-
telemetryBuilder.addTag(
522-
ContextTagKeys.AI_OPERATION_NAME.toString(),
523-
AiOperationNameSpanProcessor.getOperationName((ReadableSpan) localRootSpan));
524-
}
525-
}
526-
527-
if (applySampling) {
528-
float samplingPercentage =
529-
TelemetryUtil.getSamplingPercentage(
530-
spanContext.getTraceState(), BytecodeUtilImpl.samplingPercentage, false);
531-
532-
if (samplingPercentage != 100) {
533-
telemetryBuilder.setSampleRate(samplingPercentage);
493+
if (isPartOfTheCurrentTrace && applySampling && span instanceof ReadableSpan) {
494+
Long itemCount = ((ReadableSpan) span).getAttribute(AiSemanticAttributes.ITEM_COUNT);
495+
if (itemCount != null && itemCount != 1) {
496+
telemetryBuilder.setSampleRate(100.0f / itemCount);
534497
}
535498
}
536-
// this is not null because sdk instrumentation is not added until TelemetryClient.setActive()
537-
// is called
538-
TelemetryClient.getActive().trackAsync(telemetryBuilder.build());
539-
}
540499

541-
private static void trackAsStandalone(
542-
AbstractTelemetryBuilder telemetryBuilder, String operationId, boolean applySampling) {
543-
if (applySampling) {
544-
// sampling is done using the configured sampling percentage
500+
if (!isPartOfTheCurrentTrace && applySampling) {
501+
// standalone sampling is done using the configured sampling percentage
545502
float samplingPercentage = BytecodeUtilImpl.samplingPercentage;
546-
if (!sample(operationId, samplingPercentage)) {
503+
if (!sample(existingOperationId, samplingPercentage)) {
547504
logger.debug("Item {} sampled out", telemetryBuilder.getClass().getSimpleName());
548505
// sampled out
549506
return;
@@ -558,6 +515,33 @@ private static void trackAsStandalone(
558515
// this is not null because sdk instrumentation is not added until TelemetryClient.setActive()
559516
// is called
560517
TelemetryClient.getActive().trackAsync(telemetryBuilder.build());
518+
519+
if (featureStatsbeat != null) {
520+
featureStatsbeat.track2xBridgeUsage();
521+
}
522+
}
523+
524+
private static void setOperationTagsFromTheCurrentSpan(
525+
AbstractTelemetryBuilder telemetryBuilder,
526+
Map<String, String> tags,
527+
String existingOperationId,
528+
SpanContext spanContext,
529+
Span span) {
530+
531+
if (existingOperationId == null) {
532+
telemetryBuilder.addTag(ContextTagKeys.AI_OPERATION_ID.toString(), spanContext.getTraceId());
533+
}
534+
String existingOperationParentId = tags.get(ContextTagKeys.AI_OPERATION_PARENT_ID.toString());
535+
if (existingOperationParentId == null) {
536+
telemetryBuilder.addTag(
537+
ContextTagKeys.AI_OPERATION_PARENT_ID.toString(), spanContext.getSpanId());
538+
}
539+
String existingOperationName = tags.get(ContextTagKeys.AI_OPERATION_NAME.toString());
540+
if (existingOperationName == null && span instanceof ReadableSpan) {
541+
telemetryBuilder.addTag(
542+
ContextTagKeys.AI_OPERATION_NAME.toString(),
543+
OperationNames.getOperationName((ReadableSpan) span));
544+
}
561545
}
562546

563547
private static boolean sample(String operationId, double samplingPercentage) {
Lines changed: 8 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,25 @@
1919
* DEALINGS IN THE SOFTWARE.
2020
*/
2121

22-
package com.azure.monitor.opentelemetry.exporter.implementation.utils;
22+
package com.microsoft.applicationinsights.agent.internal.classicsdk;
2323

2424
import com.azure.monitor.opentelemetry.exporter.implementation.builders.ExceptionDetailBuilder;
2525
import com.azure.monitor.opentelemetry.exporter.implementation.builders.StackFrameBuilder;
26-
import io.opentelemetry.api.trace.TraceState;
27-
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
26+
import com.azure.monitor.opentelemetry.exporter.implementation.utils.Strings;
2827
import java.util.ArrayList;
2928
import java.util.List;
30-
import java.util.concurrent.atomic.AtomicBoolean;
3129
import javax.annotation.Nullable;
3230
import org.slf4j.Logger;
3331
import org.slf4j.LoggerFactory;
3432

35-
// naming convention:
36-
// * MonitorDomain data
37-
// * TelemetryItem telemetry
38-
public class TelemetryUtil {
33+
class TelemetryUtil {
3934

40-
private static final int MAX_PARSED_STACK_LENGTH =
41-
32768; // Breeze will reject parsedStack exceeding 65536 bytes. Each char is 2 bytes long.
35+
private static final Logger logger = LoggerFactory.getLogger(TelemetryUtil.class);
36+
37+
// Breeze will reject parsedStack exceeding 65536 bytes. Each char is 2 bytes long.
38+
private static final int MAX_PARSED_STACK_LENGTH = 32768;
4239

43-
public static List<ExceptionDetailBuilder> getExceptions(Throwable throwable) {
40+
static List<ExceptionDetailBuilder> getExceptions(Throwable throwable) {
4441
List<ExceptionDetailBuilder> exceptions = new ArrayList<>();
4542
convertExceptionTree(throwable, null, exceptions, Integer.MAX_VALUE);
4643
return exceptions;
@@ -150,74 +147,5 @@ private static int getStackFrameLength(@Nullable String text) {
150147
return text == null ? 0 : text.length();
151148
}
152149

153-
public static final String SAMPLING_PERCENTAGE_TRACE_STATE = "ai-internal-sp";
154-
155-
private static final Cache<String, OptionalFloat> parsedSamplingPercentageCache =
156-
Cache.bounded(100);
157-
158-
private static final AtomicBoolean alreadyLoggedSamplingPercentageMissing = new AtomicBoolean();
159-
private static final AtomicBoolean alreadyLoggedSamplingPercentageParseError =
160-
new AtomicBoolean();
161-
162-
private static final Logger logger = LoggerFactory.getLogger(TelemetryUtil.class);
163-
164-
public static float getSamplingPercentage(
165-
TraceState traceState, float defaultValue, boolean warnOnMissing) {
166-
String samplingPercentageStr = traceState.get(SAMPLING_PERCENTAGE_TRACE_STATE);
167-
if (samplingPercentageStr == null) {
168-
if (warnOnMissing && !alreadyLoggedSamplingPercentageMissing.getAndSet(true)) {
169-
// sampler should have set the trace state
170-
logger.warn("did not find sampling percentage in trace state: {}", traceState);
171-
}
172-
return defaultValue;
173-
}
174-
return parseSamplingPercentage(samplingPercentageStr).orElse(defaultValue);
175-
}
176-
177-
private static OptionalFloat parseSamplingPercentage(String samplingPercentageStr) {
178-
return parsedSamplingPercentageCache.computeIfAbsent(
179-
samplingPercentageStr,
180-
str -> {
181-
try {
182-
return OptionalFloat.of(Float.parseFloat(str));
183-
} catch (NumberFormatException e) {
184-
if (!alreadyLoggedSamplingPercentageParseError.getAndSet(true)) {
185-
logger.warn("error parsing sampling percentage trace state: {}", str, e);
186-
}
187-
return OptionalFloat.empty();
188-
}
189-
});
190-
}
191-
192-
private static class OptionalFloat {
193-
194-
private static final OptionalFloat EMPTY = new OptionalFloat();
195-
196-
private final boolean present;
197-
private final float value;
198-
199-
private OptionalFloat() {
200-
this.present = false;
201-
this.value = Float.NaN;
202-
}
203-
204-
private OptionalFloat(float value) {
205-
this.present = true;
206-
this.value = value;
207-
}
208-
209-
public static OptionalFloat empty() {
210-
return EMPTY;
211-
}
212-
213-
public static OptionalFloat of(float value) {
214-
return new OptionalFloat(value);
215-
}
216-
217-
public float orElse(float other) {
218-
return present ? value : other;
219-
}
220-
}
221-
222150
private TelemetryUtil() {}
223151
}

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/configuration/Configuration.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,28 @@ public static class Sampling {
156156

157157
public static class SamplingPreview {
158158

159+
// this is not the default for now at least, because
160+
//
161+
// parent not-sampled -> child not-sampled (always, to avoid broken traces)
162+
// parent sampled -> child will still sample at its desired rate
163+
// note: this is just sample rate of child, not sample rate of parent times
164+
// sample rate of child, as both are using the same trace-id hash
165+
//
166+
// ??? if child sample rate is higher than the parent sample rate
167+
// parent sampled --> child sampled
168+
// parent not-sampled --> child not-sampled
169+
// which means that child has same effective sample rate as parent, and so its item count
170+
// will be wrong
171+
//
172+
// AND SO: if want to use parent-based sampler, then need to propagate the sample rate,
173+
// otherwise can end up with incorrect math
174+
//
175+
// Another (lesser) reason is because .NET SDK always propagates trace flags "00" (not
176+
// sampled)
177+
//
178+
// IMPORTANT if changing this default, we need to keep it at least on Azure Functions
179+
public boolean parentBased;
180+
159181
public List<SamplingOverride> overrides = new ArrayList<>();
160182
}
161183

@@ -261,10 +283,8 @@ public static class PreviewConfiguration {
261283
// world,
262284
// so safer to only allow single interval for now
263285
public int metricIntervalSeconds = 60;
264-
// ignoreRemoteParentNotSampled is sometimes needed because .NET SDK always propagates trace
265-
// flags "00" (not sampled)
266-
// in particular, it is always needed in Azure Functions worker
267-
public boolean ignoreRemoteParentNotSampled = DiagnosticsHelper.rpIntegrationChar() == 'f';
286+
// this is just here to detect if using this old setting in order to give a helpful message
287+
@Deprecated public Boolean ignoreRemoteParentNotSampled;
268288
public boolean captureControllerSpans;
269289
// this is just here to detect if using this old setting in order to give a helpful message
270290
@Deprecated public boolean httpMethodInOperationName;

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/configuration/ConfigurationBuilder.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,13 @@ public static Configuration create(Path agentJarPath, @Nullable RpConfiguration
139139
"\"httpMethodInOperationName\" is no longer in preview and it is now the"
140140
+ " (one and only) default behavior");
141141
}
142+
if (config.preview.ignoreRemoteParentNotSampled != null) {
143+
configurationLogger.warn(
144+
"\"ignoreRemoteParentNotSampled\" has been deprecated and"
145+
+ " \"ignoreRemoteParentNotSampled\": false has replaced with"
146+
+ " \"preview\": { \"sampling\": { \"parentBased\": true } } (while"
147+
+ " \"ignoreRemoteParentNotSampled\": true is just the default behavior)");
148+
}
142149
if (config.preview.openTelemetryApiSupport) {
143150
configurationLogger.warn(
144151
"\"openTelemetryApiSupport\" is no longer in preview and it is now the"

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AppIdSupplier.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import com.azure.core.http.HttpResponse;
3030
import com.azure.monitor.opentelemetry.exporter.implementation.configuration.ConnectionString;
3131
import com.azure.monitor.opentelemetry.exporter.implementation.logging.NetworkFriendlyExceptions;
32-
import com.azure.monitor.opentelemetry.exporter.implementation.logging.WarningLogger;
32+
import com.azure.monitor.opentelemetry.exporter.implementation.logging.OperationLogger;
3333
import com.azure.monitor.opentelemetry.exporter.implementation.utils.ThreadPoolUtils;
3434
import com.microsoft.applicationinsights.agent.bootstrap.AiAppId;
3535
import com.microsoft.applicationinsights.agent.internal.httpclient.LazyHttpClient;
@@ -53,8 +53,8 @@ public class AppIdSupplier implements AiAppId.Supplier {
5353
Executors.newSingleThreadScheduledExecutor(
5454
ThreadPoolUtils.createDaemonThreadFactory(AppIdSupplier.class));
5555

56-
private static final WarningLogger warningLogger =
57-
new WarningLogger(GetAppIdTask.class, "Unable to retrieve appId");
56+
private static final OperationLogger operationLogger =
57+
new OperationLogger(GetAppIdTask.class, "Retrieving appId");
5858

5959
// guarded by taskLock
6060
private GetAppIdTask task;
@@ -147,7 +147,7 @@ public void run() {
147147
} catch (RuntimeException ex) {
148148
if (!NetworkFriendlyExceptions.logSpecialOneTimeFriendlyException(
149149
ex, url.toString(), friendlyExceptionThrown, logger)) {
150-
warningLogger.recordWarning("exception sending request to " + url, ex, APP_ID_ERROR);
150+
operationLogger.recordFailure("exception sending request to " + url, ex, APP_ID_ERROR);
151151
}
152152
backOff();
153153
return;
@@ -161,7 +161,7 @@ public void run() {
161161
String body = response.getBodyAsString().block();
162162
int statusCode = response.getStatusCode();
163163
if (statusCode != 200) {
164-
warningLogger.recordWarning(
164+
operationLogger.recordFailure(
165165
"received " + statusCode + " from " + url + "\nfull response:\n" + body,
166166
null,
167167
APP_ID_ERROR);
@@ -171,12 +171,12 @@ public void run() {
171171

172172
// check for case when breeze returns invalid value
173173
if (body == null || body.isEmpty()) {
174-
warningLogger.recordWarning("received empty body from " + url, null, APP_ID_ERROR);
174+
operationLogger.recordFailure("received empty body from " + url, null, APP_ID_ERROR);
175175
backOff();
176176
return;
177177
}
178178

179-
logger.debug("appId retrieved: {}", body);
179+
operationLogger.recordSuccess();
180180
appId = body;
181181
}
182182

0 commit comments

Comments
 (0)