Skip to content

Commit d5361b2

Browse files
authored
Move heartbeat to standalone exporter (#2317)
* Move heartbeat to standalone exporter module * Fix compilation * Fix spotless * Refactor * Rename * Use a simple mock * Populate default * Pass in a function * Fix micrometer test IndexOutOfBoundsException * Disable cache in test * Use Consumer<List<TelemetryItem>> to heartbeat exporter * Fix tests * Fix micrometer test
1 parent 156552d commit d5361b2

File tree

11 files changed

+89
-50
lines changed

11 files changed

+89
-50
lines changed

.github/workflows/pr.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
- name: Test
3030
uses: gradle/gradle-build-action@v2
3131
with:
32+
cache-disabled: true
3233
arguments: check assemble
3334
- name: Upload snapshot
3435
# only upload from windows since only that build includes etw

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

+15-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import com.azure.monitor.opentelemetry.exporter.implementation.MetricDataMapper;
2929
import com.azure.monitor.opentelemetry.exporter.implementation.SpanDataMapper;
3030
import com.azure.monitor.opentelemetry.exporter.implementation.configuration.ConnectionString;
31+
import com.azure.monitor.opentelemetry.exporter.implementation.heartbeat.HeartbeatExporter;
32+
import com.azure.monitor.opentelemetry.exporter.implementation.models.TelemetryItem;
3133
import com.azure.monitor.opentelemetry.exporter.implementation.quickpulse.QuickPulse;
3234
import com.azure.monitor.opentelemetry.exporter.implementation.utils.Strings;
3335
import com.azure.monitor.opentelemetry.exporter.implementation.utils.TempDirs;
@@ -41,7 +43,6 @@
4143
import com.microsoft.applicationinsights.agent.internal.exporter.AgentLogExporter;
4244
import com.microsoft.applicationinsights.agent.internal.exporter.AgentMetricExporter;
4345
import com.microsoft.applicationinsights.agent.internal.exporter.AgentSpanExporter;
44-
import com.microsoft.applicationinsights.agent.internal.heartbeat.HeartBeatProvider;
4546
import com.microsoft.applicationinsights.agent.internal.httpclient.LazyHttpClient;
4647
import com.microsoft.applicationinsights.agent.internal.legacyheaders.AiLegacyHeaderSpanProcessor;
4748
import com.microsoft.applicationinsights.agent.internal.legacyheaders.DelegatingPropagator;
@@ -57,6 +58,7 @@
5758
import com.microsoft.applicationinsights.agent.internal.telemetry.BatchItemProcessor;
5859
import com.microsoft.applicationinsights.agent.internal.telemetry.MetricFilter;
5960
import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryClient;
61+
import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryObservers;
6062
import io.opentelemetry.api.common.Attributes;
6163
import io.opentelemetry.api.common.AttributesBuilder;
6264
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder;
@@ -82,6 +84,7 @@
8284
import java.util.Collections;
8385
import java.util.List;
8486
import java.util.concurrent.TimeUnit;
87+
import java.util.function.Consumer;
8588
import java.util.stream.Collectors;
8689
import javax.annotation.Nullable;
8790
import org.slf4j.Logger;
@@ -153,7 +156,17 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) {
153156

154157
// interval longer than 15 minutes is not allowed since we use this data for usage telemetry
155158
long intervalSeconds = Math.min(config.heartbeat.intervalSeconds, MINUTES.toSeconds(15));
156-
HeartBeatProvider.start(intervalSeconds, telemetryClient);
159+
Consumer<List<TelemetryItem>> telemetryItemsConsumer =
160+
telemetryItems -> {
161+
for (TelemetryItem telemetryItem : telemetryItems) {
162+
TelemetryObservers.INSTANCE
163+
.getObservers()
164+
.forEach(consumer -> consumer.accept(telemetryItem));
165+
telemetryClient.getMetricsBatchItemProcessor().trackAsync(telemetryItem);
166+
}
167+
};
168+
HeartbeatExporter.start(
169+
intervalSeconds, telemetryClient::populateDefaults, telemetryItemsConsumer);
157170

158171
TelemetryClient.setActive(telemetryClient);
159172

agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/AzureMonitorExporterBuilder.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
package com.azure.monitor.opentelemetry.exporter;
2323

24+
import static java.util.concurrent.TimeUnit.MINUTES;
25+
2426
import com.azure.core.credential.TokenCredential;
2527
import com.azure.core.http.HttpClient;
2628
import com.azure.core.http.HttpPipeline;
@@ -40,6 +42,7 @@
4042
import com.azure.monitor.opentelemetry.exporter.implementation.MetricDataMapper;
4143
import com.azure.monitor.opentelemetry.exporter.implementation.SpanDataMapper;
4244
import com.azure.monitor.opentelemetry.exporter.implementation.configuration.ConnectionString;
45+
import com.azure.monitor.opentelemetry.exporter.implementation.heartbeat.HeartbeatExporter;
4346
import com.azure.monitor.opentelemetry.exporter.implementation.localstorage.LocalStorageStats;
4447
import com.azure.monitor.opentelemetry.exporter.implementation.localstorage.LocalStorageTelemetryPipelineListener;
4548
import com.azure.monitor.opentelemetry.exporter.implementation.models.ContextTagKeys;
@@ -267,13 +270,18 @@ public AzureMonitorTraceExporter buildTraceExporter() {
267270
* Creates an {@link AzureMonitorMetricExporter} based on the options set in the builder. This
268271
* exporter is an implementation of OpenTelemetry {@link MetricExporter}.
269272
*
273+
* <p>When a new {@link MetricExporter} is created, it will automatically start {@link
274+
* HeartbeatExporter}.
275+
*
270276
* @return An instance of {@link AzureMonitorMetricExporter}.
271277
* @throws NullPointerException if the connection string is not set on this builder or if the
272278
* environment variable "APPLICATIONINSIGHTS_CONNECTION_STRING" is not set.
273279
*/
274280
public AzureMonitorMetricExporter buildMetricExporter() {
281+
TelemetryItemExporter telemetryItemExporter = initExporterBuilder();
282+
HeartbeatExporter.start(MINUTES.toSeconds(15), t -> {}, telemetryItemExporter::send);
275283
return new AzureMonitorMetricExporter(
276-
new MetricDataMapper(instrumentationKey, t -> {}), initExporterBuilder());
284+
new MetricDataMapper(instrumentationKey, t -> {}), telemetryItemExporter);
277285
}
278286

279287
/**
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
* DEALINGS IN THE SOFTWARE.
2020
*/
2121

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

24-
import com.microsoft.applicationinsights.agent.internal.common.PropertyHelper;
24+
import com.azure.monitor.opentelemetry.exporter.implementation.utils.VersionGenerator;
2525
import java.util.HashSet;
2626
import java.util.Set;
2727
import java.util.UUID;
@@ -68,7 +68,7 @@ public DefaultHeartBeatPropertyProvider() {
6868
}
6969

7070
@Override
71-
public Callable<Boolean> setDefaultPayload(HeartBeatProvider provider) {
71+
public Callable<Boolean> setDefaultPayload(HeartbeatExporter provider) {
7272
return new Callable<Boolean>() {
7373

7474
final Set<String> enabledProperties = defaultFields;
@@ -132,7 +132,7 @@ private static String getJreVersion() {
132132

133133
/** Returns the Application Insights SDK version user is using to instrument his application. */
134134
private static String getSdkVersion() {
135-
return PropertyHelper.getQualifiedSdkVersionString();
135+
return VersionGenerator.getSdkVersion();
136136
}
137137

138138
/** Returns the OS version on which application is running. */
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* DEALINGS IN THE SOFTWARE.
2020
*/
2121

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

2424
import java.util.concurrent.Callable;
2525

@@ -42,5 +42,5 @@ public interface HeartBeatPayloadProviderInterface {
4242
* @param provider The current heartbeat provider
4343
* @return Callable which can be executed to add the payload
4444
*/
45-
Callable<Boolean> setDefaultPayload(HeartBeatProvider provider);
45+
Callable<Boolean> setDefaultPayload(HeartbeatExporter provider);
4646
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* DEALINGS IN THE SOFTWARE.
2020
*/
2121

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

2424
import javax.annotation.Nullable;
2525

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* DEALINGS IN THE SOFTWARE.
2020
*/
2121

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

2424
import java.util.ArrayList;
2525
import java.util.List;
@@ -43,7 +43,7 @@ public class HeartbeatDefaultPayload {
4343
* @param provider The HeartBeat provider
4444
* @return Callable to perform execution
4545
*/
46-
public static Callable<Boolean> populateDefaultPayload(HeartBeatProvider provider) {
46+
public static Callable<Boolean> populateDefaultPayload(HeartbeatExporter provider) {
4747
return new Callable<Boolean>() {
4848
@Override
4949
public Boolean call() throws Exception {

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/heartbeat/HeartBeatProvider.java renamed to agent/azure-monitor-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/heartbeat/HeartbeatExporter.java

+28-18
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,31 @@
1919
* DEALINGS IN THE SOFTWARE.
2020
*/
2121

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

24+
import com.azure.monitor.opentelemetry.exporter.implementation.builders.AbstractTelemetryBuilder;
2425
import com.azure.monitor.opentelemetry.exporter.implementation.builders.MetricTelemetryBuilder;
2526
import com.azure.monitor.opentelemetry.exporter.implementation.models.ContextTagKeys;
2627
import com.azure.monitor.opentelemetry.exporter.implementation.models.TelemetryItem;
2728
import com.azure.monitor.opentelemetry.exporter.implementation.utils.ThreadPoolUtils;
28-
import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryClient;
29+
import java.util.Collections;
2930
import java.util.HashMap;
31+
import java.util.List;
3032
import java.util.Map;
3133
import java.util.concurrent.ConcurrentHashMap;
3234
import java.util.concurrent.ConcurrentMap;
3335
import java.util.concurrent.ExecutorService;
3436
import java.util.concurrent.Executors;
3537
import java.util.concurrent.ScheduledExecutorService;
3638
import java.util.concurrent.TimeUnit;
39+
import java.util.function.Consumer;
3740
import org.slf4j.Logger;
3841
import org.slf4j.LoggerFactory;
3942

4043
/** Concrete implementation of Heartbeat functionality. */
41-
public class HeartBeatProvider {
44+
public class HeartbeatExporter {
4245

43-
private static final Logger logger = LoggerFactory.getLogger(HeartBeatProvider.class);
46+
private static final Logger logger = LoggerFactory.getLogger(HeartbeatExporter.class);
4447

4548
/** The name of the heartbeat metric. */
4649
private static final String HEARTBEAT_SYNTHETIC_METRIC_NAME = "HeartbeatState";
@@ -51,35 +54,42 @@ public class HeartBeatProvider {
5154
/** Map to hold heartbeat properties. */
5255
private final ConcurrentMap<String, HeartBeatPropertyPayload> heartbeatProperties;
5356

54-
/** Telemetry client instance used to send heartbeat. */
55-
private TelemetryClient telemetryClient;
57+
/** Telemetry item exporter used to send heartbeat. */
58+
private final Consumer<List<TelemetryItem>> telemetryItemsConsumer;
59+
60+
/** Telemetry builder consumer used to populate defaults properties. */
61+
private final Consumer<AbstractTelemetryBuilder> telemetryInitializer;
5662

5763
/** ThreadPool used for adding properties to concurrent dictionary. */
5864
private final ExecutorService propertyUpdateService;
5965

6066
/** Threadpool used to send data heartbeat telemetry. */
6167
private final ScheduledExecutorService heartBeatSenderService;
6268

63-
public static void start(long intervalSeconds, TelemetryClient telemetryClient) {
64-
new HeartBeatProvider(intervalSeconds, telemetryClient);
69+
public static void start(
70+
long intervalSeconds,
71+
Consumer<AbstractTelemetryBuilder> telemetryInitializer,
72+
Consumer<List<TelemetryItem>> telemetryItemsConsumer) {
73+
new HeartbeatExporter(intervalSeconds, telemetryInitializer, telemetryItemsConsumer);
6574
}
6675

67-
// visible for tests
68-
HeartBeatProvider(long intervalSeconds, TelemetryClient telemetryClient) {
76+
public HeartbeatExporter(
77+
long intervalSeconds,
78+
Consumer<AbstractTelemetryBuilder> telemetryInitializer,
79+
Consumer<List<TelemetryItem>> telemetryItemsConsumer) {
6980
this.heartbeatProperties = new ConcurrentHashMap<>();
7081
this.heartbeatsSent = 0;
7182
this.propertyUpdateService =
7283
Executors.newCachedThreadPool(
7384
ThreadPoolUtils.createDaemonThreadFactory(
74-
HeartBeatProvider.class, "propertyUpdateService"));
85+
HeartbeatExporter.class, "propertyUpdateService"));
7586
this.heartBeatSenderService =
7687
Executors.newSingleThreadScheduledExecutor(
7788
ThreadPoolUtils.createDaemonThreadFactory(
78-
HeartBeatProvider.class, "heartBeatSenderService"));
89+
HeartbeatExporter.class, "heartBeatSenderService"));
7990

80-
if (this.telemetryClient == null) {
81-
this.telemetryClient = telemetryClient;
82-
}
91+
this.telemetryItemsConsumer = telemetryItemsConsumer;
92+
this.telemetryInitializer = telemetryInitializer;
8393

8494
// Submit task to set properties to dictionary using separate thread. we do not wait for the
8595
// results to come out as some I/O bound properties may take time.
@@ -110,7 +120,7 @@ public boolean addHeartBeatProperty(
110120
/** Send the heartbeat item synchronously to application insights backend. */
111121
private void send() {
112122
try {
113-
telemetryClient.trackAsync(gatherData());
123+
telemetryItemsConsumer.accept(Collections.singletonList(gatherData()));
114124
logger.trace("No of heartbeats sent, {}", ++heartbeatsSent);
115125
} catch (RuntimeException e) {
116126
logger.warn("Error occured while sending heartbeat");
@@ -132,8 +142,8 @@ TelemetryItem gatherData() {
132142
numHealthy += payload.isHealthy() ? 0 : 1;
133143
}
134144
MetricTelemetryBuilder telemetryBuilder =
135-
telemetryClient.newMetricTelemetryBuilder(HEARTBEAT_SYNTHETIC_METRIC_NAME, numHealthy);
136-
145+
MetricTelemetryBuilder.create(HEARTBEAT_SYNTHETIC_METRIC_NAME, numHealthy);
146+
telemetryInitializer.accept(telemetryBuilder);
137147
telemetryBuilder.addTag(
138148
ContextTagKeys.AI_OPERATION_SYNTHETIC_SOURCE.toString(), HEARTBEAT_SYNTHETIC_METRIC_NAME);
139149

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* DEALINGS IN THE SOFTWARE.
2020
*/
2121

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

2424
import com.azure.monitor.opentelemetry.exporter.implementation.utils.Strings;
2525
import java.util.HashSet;
@@ -67,7 +67,7 @@ public WebAppsHeartbeatProvider() {
6767
}
6868

6969
@Override
70-
public Callable<Boolean> setDefaultPayload(HeartBeatProvider provider) {
70+
public Callable<Boolean> setDefaultPayload(HeartbeatExporter provider) {
7171
return new Callable<Boolean>() {
7272

7373
final Set<String> enabledProperties = defaultFields;

agent/agent-tooling/src/test/java/com/microsoft/applicationinsights/agent/internal/heartbeat/HeartbeatTests.java renamed to agent/azure-monitor-exporter/src/test/java/com/azure/monitor/opentelemetry/exporter/implementation/heartbeat/HeartbeatTests.java

+14-9
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,31 @@
1919
* DEALINGS IN THE SOFTWARE.
2020
*/
2121

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

2424
import static org.assertj.core.api.Assertions.assertThat;
25+
import static org.mockito.Mockito.mock;
2526

2627
import com.azure.monitor.opentelemetry.exporter.implementation.models.MetricsData;
27-
import com.microsoft.applicationinsights.agent.internal.telemetry.TelemetryClient;
28+
import com.azure.monitor.opentelemetry.exporter.implementation.models.TelemetryItem;
2829
import java.lang.reflect.Field;
30+
import java.util.List;
2931
import java.util.Set;
3032
import java.util.concurrent.ConcurrentHashMap;
3133
import java.util.concurrent.ConcurrentMap;
34+
import java.util.function.Consumer;
3235
import org.junit.jupiter.api.Test;
3336
import org.mockito.Mockito;
3437
import org.mockito.stubbing.Answer;
3538

3639
class HeartbeatTests {
3740

41+
private final Consumer<List<TelemetryItem>> telemetryItemsConsumer = mock(Consumer.class);
42+
3843
@Test
3944
void heartBeatPayloadContainsDataByDefault() throws InterruptedException {
4045
// given
41-
HeartBeatProvider provider = new HeartBeatProvider(60, TelemetryClient.createForTest());
46+
HeartbeatExporter provider = new HeartbeatExporter(60, t -> {}, telemetryItemsConsumer);
4247

4348
// some of the initialization above happens in a separate thread
4449
Thread.sleep(500);
@@ -52,7 +57,7 @@ void heartBeatPayloadContainsDataByDefault() throws InterruptedException {
5257
@Test
5358
void heartBeatPayloadContainsSpecificProperties() {
5459
// given
55-
HeartBeatProvider provider = new HeartBeatProvider(60, TelemetryClient.createForTest());
60+
HeartbeatExporter provider = new HeartbeatExporter(60, t -> {}, telemetryItemsConsumer);
5661

5762
// then
5863
assertThat(provider.addHeartBeatProperty("test", "testVal", true)).isTrue();
@@ -64,7 +69,7 @@ void heartBeatPayloadContainsSpecificProperties() {
6469
@Test
6570
void heartbeatMetricIsNonZeroWhenFailureConditionPresent() {
6671
// given
67-
HeartBeatProvider provider = new HeartBeatProvider(60, TelemetryClient.createForTest());
72+
HeartbeatExporter provider = new HeartbeatExporter(60, t -> {}, telemetryItemsConsumer);
6873

6974
// then
7075
assertThat(provider.addHeartBeatProperty("test", "testVal", false)).isTrue();
@@ -76,7 +81,7 @@ void heartbeatMetricIsNonZeroWhenFailureConditionPresent() {
7681
@Test
7782
void heartbeatMetricCountsForAllFailures() {
7883
// given
79-
HeartBeatProvider provider = new HeartBeatProvider(60, TelemetryClient.createForTest());
84+
HeartbeatExporter provider = new HeartbeatExporter(60, t -> {}, telemetryItemsConsumer);
8085

8186
// then
8287
assertThat(provider.addHeartBeatProperty("test", "testVal", false)).isTrue();
@@ -88,7 +93,7 @@ void heartbeatMetricCountsForAllFailures() {
8893

8994
@Test
9095
void sentHeartbeatContainsExpectedDefaultFields() throws Exception {
91-
HeartBeatProvider mockProvider = Mockito.mock(HeartBeatProvider.class);
96+
HeartbeatExporter mockProvider = Mockito.mock(HeartbeatExporter.class);
9297
ConcurrentMap<String, String> props = new ConcurrentHashMap<>();
9398
Mockito.when(
9499
mockProvider.addHeartBeatProperty(
@@ -116,7 +121,7 @@ void sentHeartbeatContainsExpectedDefaultFields() throws Exception {
116121
@Test
117122
void heartBeatProviderDoesNotAllowDuplicateProperties() {
118123
// given
119-
HeartBeatProvider provider = new HeartBeatProvider(60, TelemetryClient.createForTest());
124+
HeartbeatExporter provider = new HeartbeatExporter(60, t -> {}, telemetryItemsConsumer);
120125

121126
// then
122127
provider.addHeartBeatProperty("test01", "test val", true);
@@ -133,7 +138,7 @@ void cannotAddUnknownDefaultProperty() throws Exception {
133138
Set<String> defaultFields = (Set<String>) field.get(base);
134139
defaultFields.add(testKey);
135140

136-
HeartBeatProvider provider = new HeartBeatProvider(60, TelemetryClient.createForTest());
141+
HeartbeatExporter provider = new HeartbeatExporter(60, t -> {}, telemetryItemsConsumer);
137142

138143
base.setDefaultPayload(provider).call();
139144
MetricsData data = (MetricsData) provider.gatherData().getData().getBaseData();

0 commit comments

Comments
 (0)