Skip to content

Commit 9db46ce

Browse files
committed
Add gRPC export for profiles signal type
1 parent 3ad8d5f commit 9db46ce

File tree

12 files changed

+496
-1
lines changed

12 files changed

+496
-1
lines changed

exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/GrpcExporterUtil.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ static void logUnimplemented(Logger logger, String type, @Nullable String fullEr
3939
case "log":
4040
envVar = "OTEL_LOGS_EXPORTER";
4141
break;
42+
case "profile":
43+
envVar = "OTEL_PROFILES_EXPORTER";
44+
break;
4245
default:
4346
throw new IllegalStateException(
4447
"Unrecognized type, this is a programming bug in the OpenTelemetry SDK");

exporters/otlp/profiles/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,15 @@ dependencies {
1515
api(project(":exporters:common"))
1616
implementation(project(":exporters:otlp:common"))
1717

18+
implementation(project(":exporters:otlp:all"))
19+
implementation("io.grpc:grpc-stub")
20+
1821
annotationProcessor("com.google.auto.value:auto-value")
1922

2023
testCompileOnly("com.google.guava:guava")
2124
testImplementation("com.fasterxml.jackson.core:jackson-databind")
2225
testImplementation("com.google.protobuf:protobuf-java-util")
2326
testImplementation("io.opentelemetry.proto:opentelemetry-proto")
27+
testImplementation(project(":exporters:otlp:testing-internal"))
28+
testImplementation(project(":exporters:sender:okhttp"))
2429
}

exporters/otlp/profiles/src/main/java/io/opentelemetry/exporter/otlp/profiles/OtlpGrpcProfilesExporterBuilder.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import static io.opentelemetry.api.internal.Utils.checkArgument;
99
import static java.util.Objects.requireNonNull;
1010

11+
import io.grpc.ManagedChannel;
1112
import io.opentelemetry.api.metrics.MeterProvider;
1213
import io.opentelemetry.exporter.internal.compression.Compressor;
1314
import io.opentelemetry.exporter.internal.compression.CompressorProvider;
@@ -19,6 +20,7 @@
1920
import java.net.URI;
2021
import java.time.Duration;
2122
import java.util.Map;
23+
import java.util.concurrent.ExecutorService;
2224
import java.util.concurrent.TimeUnit;
2325
import java.util.function.Supplier;
2426
import javax.annotation.Nullable;
@@ -51,13 +53,32 @@ public final class OtlpGrpcProfilesExporterBuilder {
5153
this(
5254
new GrpcExporterBuilder<>(
5355
"otlp",
54-
"profiles",
56+
"profile",
5557
DEFAULT_TIMEOUT_SECS,
5658
DEFAULT_ENDPOINT,
5759
() -> MarshalerProfilesServiceGrpc::newFutureStub,
5860
GRPC_ENDPOINT_PATH));
5961
}
6062

63+
/**
64+
* Sets the managed chanel to use when communicating with the backend. Takes precedence over
65+
* {@link #setEndpoint(String)} if both are called.
66+
*
67+
* <p>Note: calling this overrides the spec compliant {@code User-Agent} header. To ensure spec
68+
* compliance, set {@link io.grpc.ManagedChannelBuilder#userAgent(String)} to {@link
69+
* OtlpUserAgent#getUserAgent()} when building the channel.
70+
*
71+
* @param channel the channel to use
72+
* @return this builder's instance
73+
* @deprecated Use {@link #setEndpoint(String)}. If you have a use case not satisfied by the
74+
* methods on this builder, please file an issue to let us know what it is.
75+
*/
76+
@Deprecated
77+
public OtlpGrpcProfilesExporterBuilder setChannel(ManagedChannel channel) {
78+
delegate.setChannel(channel);
79+
return this;
80+
}
81+
6182
/**
6283
* Sets the maximum time to wait for the collector to process an exported batch of profiles. If
6384
* unset, defaults to {@value DEFAULT_TIMEOUT_SECS}s.
@@ -181,6 +202,26 @@ public OtlpGrpcProfilesExporterBuilder setRetryPolicy(@Nullable RetryPolicy retr
181202
return this;
182203
}
183204

205+
/** Set the {@link ClassLoader} used to load the sender API. */
206+
public OtlpGrpcProfilesExporterBuilder setServiceClassLoader(ClassLoader serviceClassLoader) {
207+
requireNonNull(serviceClassLoader, "serviceClassLoader");
208+
delegate.setServiceClassLoader(serviceClassLoader);
209+
return this;
210+
}
211+
212+
/**
213+
* Set the {@link ExecutorService} used to execute requests.
214+
*
215+
* <p>NOTE: By calling this method, you are opting into managing the lifecycle of the {@code
216+
* executorService}. {@link ExecutorService#shutdown()} will NOT be called when this exporter is
217+
* shutdown.
218+
*/
219+
public OtlpGrpcProfilesExporterBuilder setExecutorService(ExecutorService executorService) {
220+
requireNonNull(executorService, "executorService");
221+
delegate.setExecutorService(executorService);
222+
return this;
223+
}
224+
184225
/**
185226
* Constructs a new instance of the exporter based on the builder's values.
186227
*
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.otlp.profiles;
7+
8+
import io.opentelemetry.api.common.Attributes;
9+
import io.opentelemetry.exporter.otlp.internal.data.ImmutableProfileData;
10+
import io.opentelemetry.exporter.otlp.internal.data.ImmutableValueTypeData;
11+
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
12+
import io.opentelemetry.sdk.resources.Resource;
13+
import java.nio.ByteBuffer;
14+
import java.util.Collections;
15+
16+
// TODO eventually merge with io.opentelemetry.exporter.otlp.testing.internal.FakeTelemetryUtil
17+
public class FakeTelemetryUtil {
18+
19+
private FakeTelemetryUtil() {}
20+
21+
private static final InstrumentationScopeInfo SCOPE_INFO =
22+
InstrumentationScopeInfo.builder("testLib")
23+
.setVersion("1.0")
24+
.setSchemaUrl("http://url")
25+
.build();
26+
27+
/** Generate a fake {@link ProfileData}. */
28+
public static ProfileData generateFakeProfileData() {
29+
String profileId = "0123456789abcdef0123456789abcdef";
30+
return ImmutableProfileData.create(
31+
Resource.create(Attributes.empty()),
32+
SCOPE_INFO,
33+
Collections.emptyList(),
34+
Collections.emptyList(),
35+
Collections.emptyList(),
36+
Collections.emptyList(),
37+
Collections.emptyList(),
38+
Collections.emptyList(),
39+
Collections.emptyList(),
40+
Collections.emptyList(),
41+
Collections.emptyList(),
42+
Collections.emptyList(),
43+
5L,
44+
6L,
45+
ImmutableValueTypeData.create(1, 2, AggregationTemporality.CUMULATIVE),
46+
7L,
47+
Collections.emptyList(),
48+
0,
49+
profileId,
50+
Collections.emptyList(),
51+
3,
52+
"format",
53+
ByteBuffer.wrap(new byte[] {4, 5}));
54+
}
55+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.otlp.profiles;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
import static org.mockito.ArgumentMatchers.same;
10+
import static org.mockito.Mockito.when;
11+
12+
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
13+
import io.opentelemetry.sdk.common.CompletableResultCode;
14+
import java.util.Arrays;
15+
import java.util.Collections;
16+
import java.util.List;
17+
import org.junit.jupiter.api.Test;
18+
import org.junit.jupiter.api.extension.ExtendWith;
19+
import org.mockito.ArgumentMatchers;
20+
import org.mockito.Mock;
21+
import org.mockito.Mockito;
22+
import org.mockito.junit.jupiter.MockitoExtension;
23+
24+
/** Unit tests for {@link MultiProfilesExporter}. */
25+
@ExtendWith(MockitoExtension.class)
26+
public class MultiProfilesExporterTest {
27+
28+
@Mock private ProfilesExporter profilesExporter1;
29+
@Mock private ProfilesExporter profilesExporter2;
30+
31+
private static final List<ProfileData> PROFILE_LIST =
32+
Collections.singletonList(FakeTelemetryUtil.generateFakeProfileData());
33+
34+
@Test
35+
void empty() {
36+
ProfilesExporter multiProfilesExporter = ProfilesExporter.composite(Collections.emptyList());
37+
multiProfilesExporter.export(PROFILE_LIST);
38+
multiProfilesExporter.shutdown();
39+
}
40+
41+
@Test
42+
void oneProfilesExporter() {
43+
ProfilesExporter multiProfilesExporter =
44+
ProfilesExporter.composite(Collections.singletonList(profilesExporter1));
45+
46+
when(profilesExporter1.export(same(PROFILE_LIST)))
47+
.thenReturn(CompletableResultCode.ofSuccess());
48+
assertThat(multiProfilesExporter.export(PROFILE_LIST).isSuccess()).isTrue();
49+
Mockito.verify(profilesExporter1).export(same(PROFILE_LIST));
50+
51+
when(profilesExporter1.flush()).thenReturn(CompletableResultCode.ofSuccess());
52+
assertThat(multiProfilesExporter.flush().isSuccess()).isTrue();
53+
Mockito.verify(profilesExporter1).flush();
54+
55+
when(profilesExporter1.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
56+
multiProfilesExporter.shutdown();
57+
Mockito.verify(profilesExporter1).shutdown();
58+
}
59+
60+
@Test
61+
void twoProfilesExporter() {
62+
ProfilesExporter multiProfilesExporter =
63+
ProfilesExporter.composite(Arrays.asList(profilesExporter1, profilesExporter2));
64+
65+
when(profilesExporter1.export(same(PROFILE_LIST)))
66+
.thenReturn(CompletableResultCode.ofSuccess());
67+
when(profilesExporter2.export(same(PROFILE_LIST)))
68+
.thenReturn(CompletableResultCode.ofSuccess());
69+
assertThat(multiProfilesExporter.export(PROFILE_LIST).isSuccess()).isTrue();
70+
Mockito.verify(profilesExporter1).export(same(PROFILE_LIST));
71+
Mockito.verify(profilesExporter2).export(same(PROFILE_LIST));
72+
73+
when(profilesExporter1.flush()).thenReturn(CompletableResultCode.ofSuccess());
74+
when(profilesExporter2.flush()).thenReturn(CompletableResultCode.ofSuccess());
75+
assertThat(multiProfilesExporter.flush().isSuccess()).isTrue();
76+
Mockito.verify(profilesExporter1).flush();
77+
Mockito.verify(profilesExporter2).flush();
78+
79+
when(profilesExporter1.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
80+
when(profilesExporter2.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
81+
multiProfilesExporter.shutdown();
82+
Mockito.verify(profilesExporter1).shutdown();
83+
Mockito.verify(profilesExporter2).shutdown();
84+
}
85+
86+
@Test
87+
void twoProfilesExporter_OneReturnFailure() {
88+
ProfilesExporter multiProfilesExporter =
89+
ProfilesExporter.composite(Arrays.asList(profilesExporter1, profilesExporter2));
90+
91+
when(profilesExporter1.export(same(PROFILE_LIST)))
92+
.thenReturn(CompletableResultCode.ofSuccess());
93+
when(profilesExporter2.export(same(PROFILE_LIST)))
94+
.thenReturn(CompletableResultCode.ofFailure());
95+
assertThat(multiProfilesExporter.export(PROFILE_LIST).isSuccess()).isFalse();
96+
Mockito.verify(profilesExporter1).export(same(PROFILE_LIST));
97+
Mockito.verify(profilesExporter2).export(same(PROFILE_LIST));
98+
99+
when(profilesExporter1.flush()).thenReturn(CompletableResultCode.ofSuccess());
100+
when(profilesExporter2.flush()).thenReturn(CompletableResultCode.ofFailure());
101+
assertThat(multiProfilesExporter.flush().isSuccess()).isFalse();
102+
Mockito.verify(profilesExporter1).flush();
103+
Mockito.verify(profilesExporter2).flush();
104+
105+
when(profilesExporter1.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
106+
when(profilesExporter2.shutdown()).thenReturn(CompletableResultCode.ofFailure());
107+
assertThat(multiProfilesExporter.shutdown().isSuccess()).isFalse();
108+
Mockito.verify(profilesExporter1).shutdown();
109+
Mockito.verify(profilesExporter2).shutdown();
110+
}
111+
112+
@Test
113+
@SuppressLogger(MultiProfilesExporter.class)
114+
void twoProfilesExporter_FirstThrows() {
115+
ProfilesExporter multiProfilesExporter =
116+
ProfilesExporter.composite(Arrays.asList(profilesExporter1, profilesExporter2));
117+
118+
Mockito.doThrow(new IllegalArgumentException("No export for you."))
119+
.when(profilesExporter1)
120+
.export(ArgumentMatchers.anyList());
121+
when(profilesExporter2.export(same(PROFILE_LIST)))
122+
.thenReturn(CompletableResultCode.ofSuccess());
123+
assertThat(multiProfilesExporter.export(PROFILE_LIST).isSuccess()).isFalse();
124+
Mockito.verify(profilesExporter1).export(same(PROFILE_LIST));
125+
Mockito.verify(profilesExporter2).export(same(PROFILE_LIST));
126+
127+
Mockito.doThrow(new IllegalArgumentException("No flush for you."))
128+
.when(profilesExporter1)
129+
.flush();
130+
when(profilesExporter2.flush()).thenReturn(CompletableResultCode.ofSuccess());
131+
assertThat(multiProfilesExporter.flush().isSuccess()).isFalse();
132+
Mockito.verify(profilesExporter1).flush();
133+
Mockito.verify(profilesExporter2).flush();
134+
135+
Mockito.doThrow(new IllegalArgumentException("No shutdown for you."))
136+
.when(profilesExporter1)
137+
.shutdown();
138+
when(profilesExporter2.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
139+
assertThat(multiProfilesExporter.shutdown().isSuccess()).isFalse();
140+
Mockito.verify(profilesExporter1).shutdown();
141+
Mockito.verify(profilesExporter2).shutdown();
142+
}
143+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.otlp.profiles;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
import org.junit.jupiter.api.Test;
11+
12+
class NoopProfilesExporterTest {
13+
14+
@Test
15+
void stringRepresentation() {
16+
assertThat(NoopProfilesExporter.getInstance()).hasToString("NoopProfilesExporter{}");
17+
}
18+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.exporter.otlp.profiles;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
import io.opentelemetry.exporter.internal.marshal.Marshaler;
11+
import io.opentelemetry.exporter.otlp.testing.internal.AbstractGrpcTelemetryExporterTest;
12+
import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporter;
13+
import io.opentelemetry.exporter.otlp.testing.internal.TelemetryExporterBuilder;
14+
import io.opentelemetry.exporter.sender.okhttp.internal.OkHttpGrpcSender;
15+
import io.opentelemetry.proto.profiles.v1development.ResourceProfiles;
16+
import java.io.Closeable;
17+
import java.util.List;
18+
import org.junit.jupiter.api.Test;
19+
20+
public class OtlpGrpcProfilesExporterTest
21+
extends AbstractGrpcTelemetryExporterTest<ProfileData, ResourceProfiles> {
22+
23+
OtlpGrpcProfilesExporterTest() {
24+
super("profile", ResourceProfiles.getDefaultInstance());
25+
}
26+
27+
@Test
28+
void usingOkHttp() throws Exception {
29+
try (Closeable exporter = OtlpGrpcProfilesExporter.builder().build()) {
30+
assertThat(exporter).extracting("delegate.grpcSender").isInstanceOf(OkHttpGrpcSender.class);
31+
}
32+
}
33+
34+
@Override
35+
protected TelemetryExporterBuilder<ProfileData> exporterBuilder() {
36+
return TelemetryExporterBuilder.wrap(OtlpGrpcProfilesExporter.builder());
37+
}
38+
39+
@Override
40+
protected TelemetryExporterBuilder<ProfileData> toBuilder(
41+
TelemetryExporter<ProfileData> exporter) {
42+
return TelemetryExporterBuilder.wrap(
43+
((OtlpGrpcProfilesExporter) exporter.unwrap()).toBuilder());
44+
}
45+
46+
@Override
47+
protected ProfileData generateFakeTelemetry() {
48+
return FakeTelemetryUtil.generateFakeProfileData();
49+
}
50+
51+
@Override
52+
protected Marshaler[] toMarshalers(List<ProfileData> telemetry) {
53+
return ResourceProfilesMarshaler.create(telemetry);
54+
}
55+
}

exporters/otlp/testing-internal/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ dependencies {
1212
api(project(":sdk:trace"))
1313
api(project(":sdk:testing"))
1414

15+
// ugly, but profiles doesn't have a separate API yet.
16+
api(project(":exporters:otlp:profiles"))
17+
1518
api(project(":exporters:otlp:all"))
1619

1720
// Must be compileOnly so gRPC isn't on the classpath for non-gRPC tests.

0 commit comments

Comments
 (0)