Skip to content

Commit 18d5a0d

Browse files
heyamstrask
andauthored
Update Statsbeat (#1859)
* Send endpoint and host as part of network statsbeat * Add unit test for getHost * Update statsbeat smoke test * Remove a fixme * Send network statbeat per url * Reuse createMetricTelemetry in tests * Send correct host when redirect happens * Fix a warning * Add unit test * Refactor * Update unit tests * Fix spotless * Add two Statsbeat configs * Fix spotless * Rename Statsbeat intervals * Send attach and azure metadata service on start * Fix spotless * Send instrumentation as a FeatureStatsbeat * Update tests * Fix spotless * Fix statsbeat smoke test * Track non-essential Statsbeat disablement * Fix smoke test failures * Fix smoke test failures * Don't send statsbeat on redirect in tests * Fix a typo * Address feedback * Remove unused method * Use uppercase enums * Init telemetry client when disabled is on * Inject StatsbeatModule to RedirectPolicy * Use initial delay * Remove null check and use Mockito.doNothing() * Move disabled to preview * Change initiaDelay to 5 seconds and revert appServers * Address feedback * Track network counter per ikey * Update tests * Handle redirect * Fix spotlessApply * Fix nullpointexception at runtime * Fix spotlessApply * Null check outside sychronize * Rename * Refactor * Fix a warning methodcanbestatic * fix test failure * Fix spotlessApply * suggestions in progress * Retrieve endpoint from redirect policy cache * Fix tests * Comment out code * Remove unused methods * Fix runtime nullpointerexception * Skip statsbeat ikey in the ikey cache map * Handle redirect * Remove null check * Remove the ikey once it's sent on redirect * Fix style violations * Remove arg duplication * More injection, less global * Comment * Remove unneeded conditions * Remove redirct for now * Remove StatsbeatModule.get() * Remove used method * Remove setinstance * Refactor * Add a todo * Replace regex * Refactor Co-authored-by: Trask Stalnaker <[email protected]>
1 parent 682619d commit 18d5a0d

File tree

22 files changed

+612
-382
lines changed

22 files changed

+612
-382
lines changed

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,16 @@ public static class Heartbeat {
162162
}
163163

164164
public static class Statsbeat {
165+
// disabledAll is used internally as an emergency kill-switch to turn off Statsbeat completely
166+
// when something goes wrong.
167+
public boolean disabledAll = false;
168+
165169
public String instrumentationKey =
166170
"c4a29126-a7cb-47e5-b348-11414998b11e"; // workspace-aistatsbeat
167171
public String endpoint =
168172
DefaultEndpoints.INGESTION_ENDPOINT; // this supports the government cloud
169-
public long intervalSeconds = MINUTES.toSeconds(15); // default to 15 minutes
170-
public long featureIntervalSeconds = DAYS.toSeconds(1); // default to daily
173+
public long shortIntervalSeconds = MINUTES.toSeconds(15); // default to 15 minutes
174+
public long longIntervalSeconds = DAYS.toSeconds(1); // default to daily
171175
}
172176

173177
public static class Proxy {
@@ -201,6 +205,7 @@ public static class PreviewConfiguration {
201205
public ProfilerConfiguration profiler = new ProfilerConfiguration();
202206
public GcEventConfiguration gcEvents = new GcEventConfiguration();
203207
public AadAuthentication authentication = new AadAuthentication();
208+
public PreviewStatsbeat statsbeat = new PreviewStatsbeat();
204209
}
205210

206211
public static class InheritedAttribute {
@@ -279,6 +284,12 @@ public static class PreviewInstrumentation {
279284
new DisabledByDefaultInstrumentation();
280285
}
281286

287+
public static class PreviewStatsbeat {
288+
// disabled is used by customer to turn off non-essential Statsbeat, e.g. disk persistence
289+
// operation status, optional network statsbeat, other endpoints except Breeze, etc.
290+
public boolean disabled = false;
291+
}
292+
282293
public static class EnabledByDefaultInstrumentation {
283294
public boolean enabled = true;
284295
}

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/exporter/Exporter.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import com.microsoft.applicationinsights.agent.internal.exporter.models.TelemetryExceptionData;
3636
import com.microsoft.applicationinsights.agent.internal.exporter.models.TelemetryExceptionDetails;
3737
import com.microsoft.applicationinsights.agent.internal.exporter.models.TelemetryItem;
38-
import com.microsoft.applicationinsights.agent.internal.statsbeat.StatsbeatModule;
3938
import com.microsoft.applicationinsights.agent.internal.telemetry.FormattedDuration;
4039
import com.microsoft.applicationinsights.agent.internal.telemetry.FormattedTime;
4140
import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryClient;
@@ -192,7 +191,10 @@ public CompletableResultCode shutdown() {
192191
private void internalExport(SpanData span) {
193192
SpanKind kind = span.getKind();
194193
String instrumentationName = span.getInstrumentationLibraryInfo().getName();
195-
StatsbeatModule.get().getNetworkStatsbeat().addInstrumentation(instrumentationName);
194+
telemetryClient
195+
.getStatsbeatModule()
196+
.getInstrumentationStatsbeat()
197+
.addInstrumentation(instrumentationName);
196198
if (kind == SpanKind.INTERNAL) {
197199
Boolean isLog = span.getAttributes().get(AI_LOG_KEY);
198200
if (isLog != null && isLog) {

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/httpclient/LazyHttpClient.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import com.azure.identity.VisualStudioCodeCredential;
4040
import com.azure.identity.VisualStudioCodeCredentialBuilder;
4141
import com.microsoft.applicationinsights.agent.internal.configuration.Configuration;
42+
import io.opentelemetry.instrumentation.api.caching.Cache;
4243
import java.net.InetSocketAddress;
4344
import java.util.ArrayList;
4445
import java.util.List;
@@ -120,12 +121,13 @@ ProxyOptions.Type.HTTP, new InetSocketAddress(proxyHost, proxyPortNumber)))
120121
return new NettyAsyncHttpClientBuilder().connectionProvider(connectionProvider).build();
121122
}
122123

124+
// pass non-null ikeyRedirectCache if you want to use ikey-specific redirect policy
123125
public static HttpPipeline newHttpPipeLine(
124126
@Nullable Configuration.AadAuthentication aadConfiguration,
125-
boolean followInstrumentationKeyForRedirect) {
127+
@Nullable Cache<String, String> ikeyRedirectCache) {
126128
List<HttpPipelinePolicy> policies = new ArrayList<>();
127-
// Redirect policy to to handle v2.1/track redirects (and other redirects too, e.g. profiler)
128-
policies.add(new RedirectPolicy(followInstrumentationKeyForRedirect));
129+
// Redirect policy to handle v2.1/track redirects (and other redirects too, e.g. profiler)
130+
policies.add(new RedirectPolicy(ikeyRedirectCache));
129131
if (aadConfiguration != null && aadConfiguration.enabled) {
130132
policies.add(getAuthenticationPolicy(aadConfiguration));
131133
}

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/httpclient/RedirectPolicy.java

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@
3636

3737
// This is mostly a copy from Azure Monitor Open Telemetry Exporter SDK AzureMonitorRedirectPolicy
3838
public final class RedirectPolicy implements HttpPipelinePolicy {
39-
private final boolean followInstrumentationKeyForRedirect;
40-
// use this only when followInstrumentationKeyForRedirect is true and instrumentation key is null
4139
private static final int PERMANENT_REDIRECT_STATUS_CODE = 308;
4240
private static final int TEMP_REDIRECT_STATUS_CODE = 307;
4341
// Based on Stamp specific redirects design doc
@@ -47,11 +45,12 @@ public final class RedirectPolicy implements HttpPipelinePolicy {
4745

4846
private final Cache<URL, String> redirectMappings =
4947
Cache.newBuilder().setMaximumSize(100).build();
50-
private final Cache<String, String> instrumentationKeyMappings =
51-
Cache.newBuilder().setMaximumSize(100).build();
5248

53-
public RedirectPolicy(boolean followInstrumentationKeyForRedirect) {
54-
this.followInstrumentationKeyForRedirect = followInstrumentationKeyForRedirect;
49+
@Nullable private final Cache<String, String> ikeyRedirectCache;
50+
51+
// pass non-null ikeyRedirectCache if you want to use ikey-specific redirect policy
52+
public RedirectPolicy(@Nullable Cache<String, String> ikeyRedirectCache) {
53+
this.ikeyRedirectCache = ikeyRedirectCache;
5554
}
5655

5756
@Override
@@ -92,24 +91,27 @@ private Mono<HttpResponse> attemptRetry(
9291
}
9392

9493
private void cacheRedirectUrl(String redirectUrl, String instrumentationKey, URL originalUrl) {
95-
if (!followInstrumentationKeyForRedirect) {
94+
if (ikeyRedirectCache == null) {
9695
redirectMappings.put(originalUrl, redirectUrl);
9796
return;
9897
}
99-
if (instrumentationKey != null) {
100-
instrumentationKeyMappings.put(instrumentationKey, redirectUrl);
98+
if (instrumentationKey == null) {
99+
throw new IllegalArgumentException(
100+
"instrumentationKey must be non-null when using ikey redirect policy");
101101
}
102+
ikeyRedirectCache.put(instrumentationKey, redirectUrl);
102103
}
103104

104105
@Nullable
105106
private String getCachedRedirectUrl(String instrumentationKey, URL originalUrl) {
106-
if (!followInstrumentationKeyForRedirect) {
107+
if (ikeyRedirectCache == null) {
107108
return redirectMappings.get(originalUrl);
108109
}
109-
if (instrumentationKey != null) {
110-
return instrumentationKeyMappings.get(instrumentationKey);
110+
if (instrumentationKey == null) {
111+
throw new IllegalArgumentException(
112+
"instrumentationKey must be non-null when using ikey redirect policy");
111113
}
112-
return null;
114+
return ikeyRedirectCache.get(instrumentationKey);
113115
}
114116

115117
/**

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import com.microsoft.applicationinsights.profiler.config.ServiceProfilerServiceConfig;
5858
import io.opentelemetry.instrumentation.api.aisdk.AiAppId;
5959
import io.opentelemetry.instrumentation.api.aisdk.AiLazyConfiguration;
60+
import io.opentelemetry.instrumentation.api.caching.Cache;
6061
import io.opentelemetry.instrumentation.api.config.Config;
6162
import io.opentelemetry.javaagent.extension.AgentListener;
6263
import io.opentelemetry.sdk.common.CompletableResultCode;
@@ -166,8 +167,16 @@ private void start(Instrumentation instrumentation) {
166167
.map(MetricFilter::new)
167168
.collect(Collectors.toList());
168169

170+
Cache<String, String> ikeyEndpointMap = Cache.newBuilder().setMaximumSize(100).build();
171+
StatsbeatModule statsbeatModule = new StatsbeatModule(ikeyEndpointMap);
172+
// TODO (heya) apply Builder design pattern to TelemetryClient
169173
TelemetryClient telemetryClient =
170-
new TelemetryClient(config.customDimensions, metricFilters, config.preview.authentication);
174+
new TelemetryClient(
175+
config.customDimensions,
176+
metricFilters,
177+
ikeyEndpointMap,
178+
statsbeatModule,
179+
config.preview.authentication);
171180
TelemetryClientInitializer.initialize(telemetryClient, config);
172181
TelemetryClient.setActive(telemetryClient);
173182

@@ -211,7 +220,7 @@ private void start(Instrumentation instrumentation) {
211220
}
212221

213222
// initialize StatsbeatModule
214-
StatsbeatModule.get().start(telemetryClient, config);
223+
statsbeatModule.start(telemetryClient, config);
215224
}
216225

217226
private static GcEventMonitor.GcEventMonitorConfiguration formGcEventMonitorConfiguration(

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/profiler/ProfilerServiceInitializer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public static synchronized void initialize(
7575
GcEventMonitor.GcEventMonitorConfiguration gcEventMonitorConfiguration) {
7676

7777
HttpPipeline httpPipeline =
78-
LazyHttpClient.newHttpPipeLine(telemetryClient.getAadAuthentication(), false);
78+
LazyHttpClient.newHttpPipeLine(telemetryClient.getAadAuthentication(), null);
7979

8080
initialize(
8181
appIdSupplier,

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/quickpulse/QuickPulse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private void initializeSync(CountDownLatch latch, TelemetryClient telemetryClien
6969
initialized = true;
7070
String quickPulseId = UUID.randomUUID().toString().replace("-", "");
7171
HttpPipeline httpPipeline =
72-
LazyHttpClient.newHttpPipeLine(telemetryClient.getAadAuthentication(), false);
72+
LazyHttpClient.newHttpPipeLine(telemetryClient.getAadAuthentication(), null);
7373
ArrayBlockingQueue<HttpRequest> sendQueue = new ArrayBlockingQueue<>(256, true);
7474

7575
QuickPulseDataSender quickPulseDataSender =

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/statsbeat/AzureMetadataService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ void scheduleWithFixedDelay(long interval) {
6868
// Querying Azure Metadata Service is required for every 15 mins since VM id will get updated
6969
// frequently.
7070
// Starting and restarting a VM will generate a new VM id each time.
71-
scheduledExecutor.scheduleWithFixedDelay(this, interval, interval, TimeUnit.SECONDS);
71+
scheduledExecutor.scheduleWithFixedDelay(this, 60, interval, TimeUnit.SECONDS);
7272
}
7373

7474
// only used by tests

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/statsbeat/Feature.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ enum Feature {
4848
RABBITMQ_DISABLED(18),
4949
SPRING_INTEGRATION_DISABLED(19),
5050
LEGACY_PROPAGATION_DISABLED(20),
51-
GRIZZLY_DISABLED(21); // preview instrumentation
51+
GRIZZLY_DISABLED(21), // preview instrumentation
52+
STATSBEAT_DISABLED(22); // disable non-essential statsbeat
5253

5354
private static final Map<String, Feature> javaVendorFeatureMap;
5455

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/statsbeat/FeatureStatsbeat.java

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,25 @@
2525
import com.microsoft.applicationinsights.agent.internal.exporter.models.TelemetryItem;
2626
import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryClient;
2727
import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryUtil;
28-
import java.util.HashSet;
28+
import java.util.Collections;
29+
import java.util.Map;
2930
import java.util.Set;
31+
import java.util.concurrent.ConcurrentHashMap;
3032

31-
class FeatureStatsbeat extends BaseStatsbeat {
33+
public class FeatureStatsbeat extends BaseStatsbeat {
3234

3335
private static final String FEATURE_METRIC_NAME = "Feature";
36+
private static final String INSTRUMENTATION_METRIC_NAME = "Instrumentation";
3437

35-
private final Set<Feature> featureList = new HashSet<>(64);
38+
private final Set<Feature> featureList = Collections.newSetFromMap(new ConcurrentHashMap<>());
39+
private final Set<String> instrumentationList =
40+
Collections.newSetFromMap(new ConcurrentHashMap<>());
41+
private final FeatureType type;
3642

37-
FeatureStatsbeat(CustomDimensions customDimensions) {
43+
FeatureStatsbeat(CustomDimensions customDimensions, FeatureType type) {
3844
// track java distribution
3945
super(customDimensions);
46+
this.type = type;
4047
String javaVendor = System.getProperty("java.vendor");
4148
featureList.add(Feature.fromJavaVendor(javaVendor));
4249
}
@@ -46,13 +53,42 @@ long getFeature() {
4653
return Feature.encode(featureList);
4754
}
4855

56+
/**
57+
* Returns a long that represents a list of instrumentations. Each bitfield maps to an
58+
* instrumentation.
59+
*/
60+
long getInstrumentation() {
61+
return Instrumentations.encode(instrumentationList);
62+
}
63+
64+
// this is used by Exporter
65+
public void addInstrumentation(String instrumentation) {
66+
instrumentationList.add(instrumentation);
67+
}
68+
4969
@Override
5070
protected void send(TelemetryClient telemetryClient) {
51-
TelemetryItem statsbeatTelemetry =
52-
createStatsbeatTelemetry(telemetryClient, FEATURE_METRIC_NAME, 0);
53-
TelemetryUtil.getProperties(statsbeatTelemetry.getData().getBaseData())
54-
.put("feature", String.valueOf(getFeature()));
55-
telemetryClient.trackStatsbeatAsync(statsbeatTelemetry);
71+
String metricName;
72+
long encodedLong;
73+
String featureType;
74+
75+
if (type == FeatureType.FEATURE) {
76+
metricName = FEATURE_METRIC_NAME;
77+
encodedLong = getFeature();
78+
featureType = "feature";
79+
} else {
80+
metricName = INSTRUMENTATION_METRIC_NAME;
81+
encodedLong = getInstrumentation();
82+
featureType = "instrumentation";
83+
}
84+
85+
TelemetryItem telemetryItem = createStatsbeatTelemetry(telemetryClient, metricName, 0);
86+
Map<String, String> properties =
87+
TelemetryUtil.getProperties(telemetryItem.getData().getBaseData());
88+
properties.put("feature", String.valueOf(encodedLong));
89+
properties.put("type", featureType);
90+
91+
telemetryClient.trackStatsbeatAsync(telemetryItem);
5692
}
5793

5894
void trackConfigurationOptions(Configuration config) {
@@ -102,5 +138,10 @@ void trackConfigurationOptions(Configuration config) {
102138
if (!config.preview.instrumentation.springIntegration.enabled) {
103139
featureList.add(Feature.SPRING_INTEGRATION_DISABLED);
104140
}
141+
142+
// Statsbeat
143+
if (config.preview.statsbeat.disabled) {
144+
featureList.add(Feature.STATSBEAT_DISABLED);
145+
}
105146
}
106147
}

0 commit comments

Comments
 (0)