55
66package io .opentelemetry .exporter .internal .grpc ;
77
8+ import static org .assertj .core .api .Assertions .assertThat ;
89import static org .assertj .core .api .Assertions .assertThatThrownBy ;
10+ import static org .mockito .ArgumentMatchers .any ;
11+ import static org .mockito .Mockito .doAnswer ;
912
13+ import io .opentelemetry .api .common .Attributes ;
1014import io .opentelemetry .exporter .internal .ExporterMetrics ;
15+ import java .io .IOException ;
1116import java .net .URI ;
17+ import java .util .function .Consumer ;
18+ import io .opentelemetry .exporter .internal .marshal .Marshaler ;
19+ import io .opentelemetry .sdk .common .HealthMetricLevel ;
20+ import io .opentelemetry .sdk .internal .ComponentId ;
21+ import io .opentelemetry .sdk .internal .SemConvAttributes ;
22+ import io .opentelemetry .sdk .metrics .SdkMeterProvider ;
23+ import io .opentelemetry .sdk .testing .assertj .OpenTelemetryAssertions ;
24+ import io .opentelemetry .sdk .testing .exporter .InMemoryMetricReader ;
1225import org .junit .jupiter .api .Test ;
26+ import org .junit .jupiter .params .ParameterizedTest ;
27+ import org .junit .jupiter .params .provider .EnumSource ;
28+ import org .mockito .Mockito ;
1329
1430class GrpcExporterTest {
1531
@@ -31,4 +47,204 @@ void build_NoGrpcSenderProvider() {
3147 "No GrpcSenderProvider found on classpath. Please add dependency on "
3248 + "opentelemetry-exporter-sender-okhttp or opentelemetry-exporter-sender-grpc-upstream" );
3349 }
50+
51+ @ ParameterizedTest
52+ @ EnumSource
53+ @ SuppressWarnings ("unchecked" )
54+ void testHealthMetrics (ExporterMetrics .Signal signal ) {
55+ String signalMetricPrefix ;
56+ String expectedUnit ;
57+ switch (signal ) {
58+ case SPAN :
59+ signalMetricPrefix = "otel.sdk.exporter.span." ;
60+ expectedUnit = "{span}" ;
61+ break ;
62+ case LOG :
63+ signalMetricPrefix = "otel.sdk.exporter.log." ;
64+ expectedUnit = "{log_record}" ;
65+ break ;
66+ case METRIC :
67+ signalMetricPrefix = "otel.sdk.exporter.metric." ;
68+ expectedUnit = "{data_point}" ;
69+ break ;
70+ default :
71+ throw new IllegalStateException ();
72+ }
73+
74+ InMemoryMetricReader inMemoryMetrics = InMemoryMetricReader .create ();
75+ try (SdkMeterProvider meterProvider =
76+ SdkMeterProvider .builder ().registerMetricReader (inMemoryMetrics ).build ()) {
77+
78+ ComponentId id = ComponentId .generateLazy ("test_exporter" );
79+
80+ Attributes expectedAttributes =
81+ Attributes .builder ()
82+ .put (SemConvAttributes .OTEL_COMPONENT_TYPE , "test_exporter" )
83+ .put (SemConvAttributes .OTEL_COMPONENT_NAME , id .getComponentName ())
84+ .build ();
85+
86+ GrpcSender <Marshaler > mockSender = Mockito .mock (GrpcSender .class );
87+ Marshaler mockMarshaller = Mockito .mock (Marshaler .class );
88+
89+ GrpcExporter <Marshaler > exporter =
90+ new GrpcExporter <Marshaler >(
91+ "legacy_exporter" ,
92+ signal ,
93+ mockSender ,
94+ HealthMetricLevel .ON ,
95+ id ,
96+ () -> meterProvider
97+ );
98+
99+ doAnswer (
100+ invoc -> {
101+ Consumer <GrpcResponse > onResponse = invoc .getArgument (1 );
102+
103+ assertThat (inMemoryMetrics .collectAllMetrics ())
104+ .hasSize (1 )
105+ .anySatisfy (
106+ metric ->
107+ OpenTelemetryAssertions .assertThat (metric )
108+ .hasName (signalMetricPrefix + "inflight" )
109+ .hasUnit (expectedUnit )
110+ .hasLongSumSatisfying (
111+ ma ->
112+ ma .isNotMonotonic ()
113+ .hasPointsSatisfying (
114+ pa ->
115+ pa .hasAttributes (expectedAttributes )
116+ .hasValue (42 ))));
117+
118+ onResponse .accept (GrpcResponse .create (0 , null ));
119+ return null ;
120+ })
121+ .when (mockSender )
122+ .send (any (), any (), any ());
123+
124+ exporter .export (mockMarshaller , 42 );
125+
126+ doAnswer (
127+ invoc -> {
128+ Consumer <GrpcResponse > onResponse = invoc .getArgument (1 );
129+ onResponse .accept (GrpcResponse .create (GrpcExporterUtil .GRPC_STATUS_UNAVAILABLE , null ));
130+ return null ;
131+ })
132+ .when (mockSender )
133+ .send (any (),any (), any ());
134+ exporter .export (mockMarshaller , 15 );
135+
136+ doAnswer (
137+ invoc -> {
138+ Consumer <Throwable > onError = invoc .getArgument (2 );
139+ onError .accept (new IOException ("Computer says no" ));
140+ return null ;
141+ })
142+ .when (mockSender )
143+ .send (any (), any (), any ());
144+ exporter .export (mockMarshaller , 7 );
145+
146+ assertThat (inMemoryMetrics .collectAllMetrics ())
147+ .hasSize (3 )
148+ .anySatisfy (
149+ metric ->
150+ OpenTelemetryAssertions .assertThat (metric )
151+ .hasName (signalMetricPrefix + "inflight" )
152+ .hasUnit (expectedUnit )
153+ .hasLongSumSatisfying (
154+ ma ->
155+ ma .hasPointsSatisfying (
156+ pa -> pa .hasAttributes (expectedAttributes ).hasValue (0 ))))
157+ .anySatisfy (
158+ metric ->
159+ OpenTelemetryAssertions .assertThat (metric )
160+ .hasName (signalMetricPrefix + "exported" )
161+ .hasUnit (expectedUnit )
162+ .hasLongSumSatisfying (
163+ ma ->
164+ ma .hasPointsSatisfying (
165+ pa -> pa .hasAttributes (expectedAttributes ).hasValue (42 ),
166+ pa ->
167+ pa .hasAttributes (
168+ expectedAttributes .toBuilder ()
169+ .put (SemConvAttributes .ERROR_TYPE , "" +GrpcExporterUtil .GRPC_STATUS_UNAVAILABLE )
170+ .build ())
171+ .hasValue (15 ),
172+ pa ->
173+ pa .hasAttributes (
174+ expectedAttributes .toBuilder ()
175+ .put (
176+ SemConvAttributes .ERROR_TYPE ,
177+ "java.io.IOException" )
178+ .build ())
179+ .hasValue (7 ))))
180+ .anySatisfy (
181+ metric ->
182+ OpenTelemetryAssertions .assertThat (metric )
183+ .hasName ("otel.sdk.exporter.operation.duration" )
184+ .hasUnit ("s" )
185+ .hasHistogramSatisfying (
186+ ma ->
187+ ma .hasPointsSatisfying (
188+ pa -> pa .hasAttributes (expectedAttributes ).hasBucketCounts (1 ),
189+ pa ->
190+ pa .hasAttributes (
191+ expectedAttributes .toBuilder ()
192+ .put (SemConvAttributes .ERROR_TYPE , "" +GrpcExporterUtil .GRPC_STATUS_UNAVAILABLE )
193+ .build ())
194+ .hasBucketCounts (1 ),
195+ pa ->
196+ pa .hasAttributes (
197+ expectedAttributes .toBuilder ()
198+ .put (
199+ SemConvAttributes .ERROR_TYPE ,
200+ "java.io.IOException" )
201+ .build ())
202+ .hasBucketCounts (1 ))));
203+ }
204+ }
205+
206+ @ Test
207+ @ SuppressWarnings ("unchecked" )
208+ void testHealthMetricsDisabled () {
209+ InMemoryMetricReader inMemoryMetrics = InMemoryMetricReader .create ();
210+ try (SdkMeterProvider meterProvider =
211+ SdkMeterProvider .builder ().registerMetricReader (inMemoryMetrics ).build ()) {
212+
213+ ComponentId id = ComponentId .generateLazy ("test_exporter" );
214+ GrpcSender <Marshaler > mockSender = Mockito .mock (GrpcSender .class );
215+ Marshaler mockMarshaller = Mockito .mock (Marshaler .class );
216+
217+ GrpcExporter <Marshaler > exporter =
218+ new GrpcExporter <Marshaler >(
219+ "legacy_exporter" ,
220+ ExporterMetrics .Signal .SPAN ,
221+ mockSender ,
222+ HealthMetricLevel .OFF ,
223+ id ,
224+ () -> meterProvider
225+ );
226+
227+ doAnswer (
228+ invoc -> {
229+ Consumer <GrpcResponse > onResponse = invoc .getArgument (1 );
230+ onResponse .accept (GrpcResponse .create (0 , null ));
231+ return null ;
232+ })
233+ .when (mockSender )
234+ .send (any (), any (), any ());
235+ exporter .export (mockMarshaller , 42 );
236+
237+ doAnswer (
238+ invoc -> {
239+ Consumer <GrpcResponse > onResponse = invoc .getArgument (1 );
240+ onResponse .accept (GrpcResponse .create (GrpcExporterUtil .GRPC_STATUS_UNAVAILABLE , null ));
241+ return null ;
242+ })
243+ .when (mockSender )
244+ .send (any (), any (), any ());
245+ exporter .export (mockMarshaller , 42 );
246+
247+ assertThat (inMemoryMetrics .collectAllMetrics ()).isEmpty ();
248+ }
249+ }
34250}
0 commit comments