blob: c0c24a9b5ed46afc982421f0b3b36163b4d6ec49 [file] [log] [blame]
Kun Zhang49bde542017-04-25 13:53:29 -07001/*
Carl Mastrangelo3bfd6302017-05-31 13:29:01 -07002 * Copyright 2017, gRPC Authors All rights reserved.
Kun Zhang49bde542017-04-25 13:53:29 -07003 *
Carl Mastrangelo3bfd6302017-05-31 13:29:01 -07004 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
Carl Mastrangelo166108a2017-06-01 14:28:37 -07007 *
Carl Mastrangelo3bfd6302017-05-31 13:29:01 -07008 * http://www.apache.org/licenses/LICENSE-2.0
Carl Mastrangelo166108a2017-06-01 14:28:37 -07009 *
Carl Mastrangelo3bfd6302017-05-31 13:29:01 -070010 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
Kun Zhang49bde542017-04-25 13:53:29 -070015 */
16
17package io.grpc.internal;
18
19import static java.util.concurrent.TimeUnit.MILLISECONDS;
20import static org.junit.Assert.assertEquals;
21import static org.junit.Assert.assertFalse;
22import static org.junit.Assert.assertNotNull;
23import static org.junit.Assert.assertNotSame;
24import static org.junit.Assert.assertNull;
25import static org.junit.Assert.assertSame;
26import static org.junit.Assert.assertTrue;
27import static org.junit.Assert.fail;
28import static org.mockito.Matchers.any;
29import static org.mockito.Matchers.anyString;
Bogdan Drutu530b7142017-05-23 15:10:36 -070030import static org.mockito.Matchers.argThat;
Kun Zhang49bde542017-04-25 13:53:29 -070031import static org.mockito.Matchers.eq;
32import static org.mockito.Matchers.isNull;
33import static org.mockito.Matchers.same;
34import static org.mockito.Mockito.never;
35import static org.mockito.Mockito.spy;
36import static org.mockito.Mockito.verify;
37import static org.mockito.Mockito.verifyNoMoreInteractions;
38import static org.mockito.Mockito.verifyZeroInteractions;
39import static org.mockito.Mockito.when;
40
41import com.google.instrumentation.stats.RpcConstants;
42import com.google.instrumentation.stats.StatsContext;
43import com.google.instrumentation.stats.TagValue;
44import com.google.instrumentation.trace.Annotation;
45import com.google.instrumentation.trace.AttributeValue;
46import com.google.instrumentation.trace.BinaryPropagationHandler;
47import com.google.instrumentation.trace.ContextUtils;
48import com.google.instrumentation.trace.EndSpanOptions;
49import com.google.instrumentation.trace.Link;
50import com.google.instrumentation.trace.NetworkEvent;
51import com.google.instrumentation.trace.Span;
52import com.google.instrumentation.trace.SpanContext;
53import com.google.instrumentation.trace.SpanFactory;
54import com.google.instrumentation.trace.SpanId;
55import com.google.instrumentation.trace.StartSpanOptions;
56import com.google.instrumentation.trace.TraceId;
57import com.google.instrumentation.trace.TraceOptions;
58import com.google.instrumentation.trace.Tracer;
59import io.grpc.CallOptions;
60import io.grpc.Channel;
61import io.grpc.ClientCall;
62import io.grpc.ClientInterceptor;
63import io.grpc.ClientInterceptors;
64import io.grpc.ClientStreamTracer;
65import io.grpc.Context;
66import io.grpc.Metadata;
67import io.grpc.MethodDescriptor;
68import io.grpc.ServerCall;
69import io.grpc.ServerCallHandler;
70import io.grpc.ServerServiceDefinition;
71import io.grpc.ServerStreamTracer;
72import io.grpc.Status;
73import io.grpc.internal.testing.StatsTestUtils;
74import io.grpc.internal.testing.StatsTestUtils.FakeStatsContextFactory;
75import io.grpc.testing.GrpcServerRule;
76import java.io.ByteArrayInputStream;
77import java.io.InputStream;
78import java.text.ParseException;
79import java.util.Map;
80import java.util.Random;
81import java.util.concurrent.atomic.AtomicReference;
82import javax.annotation.Nullable;
83import org.junit.After;
84import org.junit.Before;
85import org.junit.Rule;
86import org.junit.Test;
87import org.junit.runner.RunWith;
88import org.junit.runners.JUnit4;
89import org.mockito.ArgumentCaptor;
Bogdan Drutu530b7142017-05-23 15:10:36 -070090import org.mockito.ArgumentMatcher;
Kun Zhang49bde542017-04-25 13:53:29 -070091import org.mockito.Captor;
92import org.mockito.Mock;
93import org.mockito.MockitoAnnotations;
94
95/**
96 * Test for {@link CensusStatsModule} and {@link CensusTracingModule}.
97 */
98@RunWith(JUnit4.class)
99public class CensusModulesTest {
100 private static final CallOptions.Key<String> CUSTOM_OPTION =
101 CallOptions.Key.of("option1", "default");
102 private static final CallOptions CALL_OPTIONS =
103 CallOptions.DEFAULT.withOption(CUSTOM_OPTION, "customvalue");
104
105 private static class StringInputStream extends InputStream {
106 final String string;
107
108 StringInputStream(String string) {
109 this.string = string;
110 }
111
112 @Override
113 public int read() {
114 // InProcessTransport doesn't actually read bytes from the InputStream. The InputStream is
115 // passed to the InProcess server and consumed by MARSHALLER.parse().
116 throw new UnsupportedOperationException("Should not be called");
117 }
118 }
119
120 private static final MethodDescriptor.Marshaller<String> MARSHALLER =
121 new MethodDescriptor.Marshaller<String>() {
122 @Override
123 public InputStream stream(String value) {
124 return new StringInputStream(value);
125 }
126
127 @Override
128 public String parse(InputStream stream) {
129 return ((StringInputStream) stream).string;
130 }
131 };
132
133 private final MethodDescriptor<String, String> method = MethodDescriptor.create(
134 MethodDescriptor.MethodType.UNKNOWN, "package1.service2/method3",
135 MARSHALLER, MARSHALLER);
136 private final FakeClock fakeClock = new FakeClock();
137 private final FakeStatsContextFactory statsCtxFactory = new FakeStatsContextFactory();
138 private final Random random = new Random(0);
139 private final SpanContext fakeClientSpanContext =
140 SpanContext.create(
141 TraceId.generateRandomId(random), SpanId.generateRandomId(random),
142 TraceOptions.builder().build());
143 private final SpanContext fakeClientParentSpanContext =
144 SpanContext.create(
145 TraceId.generateRandomId(random), SpanId.generateRandomId(random),
146 TraceOptions.builder().build());
147 private final SpanContext fakeServerSpanContext =
148 SpanContext.create(
149 TraceId.generateRandomId(random), SpanId.generateRandomId(random),
150 TraceOptions.builder().build());
151 private final SpanContext fakeServerParentSpanContext =
152 SpanContext.create(
153 TraceId.generateRandomId(random), SpanId.generateRandomId(random),
154 TraceOptions.builder().build());
155 private final Span fakeClientSpan = new FakeSpan(fakeClientSpanContext);
156 private final Span fakeServerSpan = new FakeSpan(fakeServerSpanContext);
157 private final Span fakeClientParentSpan = new FakeSpan(fakeClientParentSpanContext);
158 private final Span fakeServerParentSpan = new FakeSpan(fakeServerParentSpanContext);
159 private final Span spyClientSpan = spy(fakeClientSpan);
160 private final Span spyServerSpan = spy(fakeServerSpan);
161 private final byte[] binarySpanContext = new byte[]{3, 1, 5};
Bogdan Drutu530b7142017-05-23 15:10:36 -0700162 private final ArgumentMatcher<StartSpanOptions> startSpanOptionsMatcher =
163 new ArgumentMatcher<StartSpanOptions>() {
164 @Override
165 public boolean matches(Object argument) {
166 return Boolean.TRUE.equals(((StartSpanOptions) argument).getRecordEvents());
167 }
168 };
Kun Zhang49bde542017-04-25 13:53:29 -0700169
170 @Rule
171 public final GrpcServerRule grpcServerRule = new GrpcServerRule().directExecutor();
172
173 @Mock
174 private AccessibleSpanFactory mockSpanFactory;
175 @Mock
176 private BinaryPropagationHandler mockTracingPropagationHandler;
177 @Mock
178 private ClientCall.Listener<String> mockClientCallListener;
179 @Mock
180 private ServerCall.Listener<String> mockServerCallListener;
181 @Captor
182 private ArgumentCaptor<CallOptions> callOptionsCaptor;
183 @Captor
184 private ArgumentCaptor<ClientCall.Listener<String>> clientCallListenerCaptor;
185 @Captor
186 private ArgumentCaptor<Status> statusCaptor;
187
188 private Tracer tracer;
189 private CensusStatsModule censusStats;
190 private CensusTracingModule censusTracing;
191
192 @Before
193 @SuppressWarnings("unchecked")
194 public void setUp() throws Exception {
195 MockitoAnnotations.initMocks(this);
196 when(mockSpanFactory.startSpan(any(Span.class), anyString(), any(StartSpanOptions.class)))
197 .thenReturn(spyClientSpan);
198 when(
199 mockSpanFactory.startSpanWithRemoteParent(
200 any(SpanContext.class), anyString(), any(StartSpanOptions.class)))
201 .thenReturn(spyServerSpan);
202 when(mockTracingPropagationHandler.toBinaryValue(any(SpanContext.class)))
203 .thenReturn(binarySpanContext);
204 when(mockTracingPropagationHandler.fromBinaryValue(any(byte[].class)))
205 .thenReturn(fakeServerParentSpanContext);
206 tracer = new Tracer(mockSpanFactory) {};
Kun Zhangbe74e972017-04-26 10:50:55 -0700207 censusStats = new CensusStatsModule(statsCtxFactory, fakeClock.getStopwatchSupplier(), true);
Kun Zhang49bde542017-04-25 13:53:29 -0700208 censusTracing = new CensusTracingModule(tracer, mockTracingPropagationHandler);
209 }
210
211 @After
212 public void wrapUp() {
213 assertNull(statsCtxFactory.pollRecord());
214 }
215
216 @Test
217 public void clientInterceptorNoCustomTag() {
218 testClientInterceptors(false);
219 }
220
221 @Test
222 public void clientInterceptorCustomTag() {
223 testClientInterceptors(true);
224 }
225
226 // Test that Census ClientInterceptors uses the StatsContext and Span out of the current Context
227 // to create the ClientCallTracer, and that it intercepts ClientCall.Listener.onClose() to call
228 // ClientCallTracer.callEnded().
229 private void testClientInterceptors(boolean nonDefaultContext) {
230 grpcServerRule.getServiceRegistry().addService(
231 ServerServiceDefinition.builder("package1.service2").addMethod(
232 method, new ServerCallHandler<String, String>() {
233 @Override
234 public ServerCall.Listener<String> startCall(
235 ServerCall<String, String> call, Metadata headers) {
236 call.sendHeaders(new Metadata());
237 call.sendMessage("Hello");
238 call.close(
239 Status.PERMISSION_DENIED.withDescription("No you don't"), new Metadata());
240 return mockServerCallListener;
241 }
242 }).build());
243
244 final AtomicReference<CallOptions> capturedCallOptions = new AtomicReference<CallOptions>();
245 ClientInterceptor callOptionsCaptureInterceptor = new ClientInterceptor() {
246 @Override
247 public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
248 MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
249 capturedCallOptions.set(callOptions);
250 return next.newCall(method, callOptions);
251 }
252 };
253 Channel interceptedChannel =
254 ClientInterceptors.intercept(
255 grpcServerRule.getChannel(), callOptionsCaptureInterceptor,
256 censusStats.getClientInterceptor(), censusTracing.getClientInterceptor());
257 ClientCall<String, String> call;
258 if (nonDefaultContext) {
259 Context ctx =
260 Context.ROOT.withValues(
261 CensusStatsModule.STATS_CONTEXT_KEY,
262 statsCtxFactory.getDefault().with(
263 StatsTestUtils.EXTRA_TAG, TagValue.create("extra value")),
264 ContextUtils.CONTEXT_SPAN_KEY,
265 fakeClientParentSpan);
266 Context origCtx = ctx.attach();
267 try {
268 call = interceptedChannel.newCall(method, CALL_OPTIONS);
269 } finally {
270 ctx.detach(origCtx);
271 }
272 } else {
273 assertNull(CensusStatsModule.STATS_CONTEXT_KEY.get());
274 assertNull(ContextUtils.CONTEXT_SPAN_KEY.get());
275 call = interceptedChannel.newCall(method, CALL_OPTIONS);
276 }
277
278 // The interceptor adds tracer factory to CallOptions
279 assertEquals("customvalue", capturedCallOptions.get().getOption(CUSTOM_OPTION));
280 assertEquals(2, capturedCallOptions.get().getStreamTracerFactories().size());
281 assertTrue(
282 capturedCallOptions.get().getStreamTracerFactories().get(0)
283 instanceof CensusTracingModule.ClientCallTracer);
284 assertTrue(
285 capturedCallOptions.get().getStreamTracerFactories().get(1)
286 instanceof CensusStatsModule.ClientCallTracer);
287
288 // Make the call
289 Metadata headers = new Metadata();
290 call.start(mockClientCallListener, headers);
291 assertNull(statsCtxFactory.pollRecord());
292 if (nonDefaultContext) {
293 verify(mockSpanFactory).startSpan(
294 same(fakeClientParentSpan), eq("Sent.package1.service2.method3"),
Bogdan Drutu530b7142017-05-23 15:10:36 -0700295 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700296 } else {
297 verify(mockSpanFactory).startSpan(
Bogdan Drutu530b7142017-05-23 15:10:36 -0700298 isNull(Span.class), eq("Sent.package1.service2.method3"),
299 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700300 }
301 verify(spyClientSpan, never()).end(any(EndSpanOptions.class));
302
303 // End the call
304 call.halfClose();
305 call.request(1);
306
307 verify(mockClientCallListener).onClose(statusCaptor.capture(), any(Metadata.class));
308 Status status = statusCaptor.getValue();
309 assertEquals(Status.Code.PERMISSION_DENIED, status.getCode());
310 assertEquals("No you don't", status.getDescription());
311
312 // The intercepting listener calls callEnded() on ClientCallTracer, which records to Census.
313 StatsTestUtils.MetricsRecord record = statsCtxFactory.pollRecord();
314 assertNotNull(record);
315 TagValue methodTag = record.tags.get(RpcConstants.RPC_CLIENT_METHOD);
316 assertEquals(method.getFullMethodName(), methodTag.toString());
317 TagValue statusTag = record.tags.get(RpcConstants.RPC_STATUS);
318 assertEquals(Status.Code.PERMISSION_DENIED.toString(), statusTag.toString());
319 if (nonDefaultContext) {
320 TagValue extraTag = record.tags.get(StatsTestUtils.EXTRA_TAG);
321 assertEquals("extra value", extraTag.toString());
322 } else {
323 assertNull(record.tags.get(StatsTestUtils.EXTRA_TAG));
324 }
325 verify(spyClientSpan).end(
326 EndSpanOptions.builder()
327 .setStatus(
328 com.google.instrumentation.trace.Status.PERMISSION_DENIED
329 .withDescription("No you don't"))
330 .build());
331 verify(spyClientSpan, never()).end();
332 }
333
334 @Test
335 public void clientBasicStatsDefaultContext() {
336 CensusStatsModule.ClientCallTracer callTracer =
337 censusStats.newClientCallTracer(statsCtxFactory.getDefault(), method.getFullMethodName());
338 Metadata headers = new Metadata();
339 ClientStreamTracer tracer = callTracer.newClientStreamTracer(headers);
340
341 fakeClock.forwardTime(30, MILLISECONDS);
342 tracer.outboundHeaders();
343
344 fakeClock.forwardTime(100, MILLISECONDS);
345 tracer.outboundWireSize(1028);
346 tracer.outboundUncompressedSize(1128);
347
348 fakeClock.forwardTime(16, MILLISECONDS);
349 tracer.inboundWireSize(33);
350 tracer.inboundUncompressedSize(67);
351 tracer.outboundWireSize(99);
352 tracer.outboundUncompressedSize(865);
353
354 fakeClock.forwardTime(24, MILLISECONDS);
355 tracer.inboundWireSize(154);
356 tracer.inboundUncompressedSize(552);
357 tracer.streamClosed(Status.OK);
358 callTracer.callEnded(Status.OK);
359
360 StatsTestUtils.MetricsRecord record = statsCtxFactory.pollRecord();
361 assertNotNull(record);
362 assertNoServerContent(record);
363 TagValue methodTag = record.tags.get(RpcConstants.RPC_CLIENT_METHOD);
364 assertEquals(method.getFullMethodName(), methodTag.toString());
365 TagValue statusTag = record.tags.get(RpcConstants.RPC_STATUS);
366 assertEquals(Status.Code.OK.toString(), statusTag.toString());
367 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_ERROR_COUNT));
368 assertEquals(1028 + 99, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_REQUEST_BYTES));
369 assertEquals(1128 + 865,
370 record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
371 assertEquals(33 + 154, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_RESPONSE_BYTES));
372 assertEquals(67 + 552,
373 record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
374 assertEquals(30 + 100 + 16 + 24,
375 record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
376 }
377
378 @Test
379 public void clientBasicTracingDefaultSpan() {
380 CensusTracingModule.ClientCallTracer callTracer =
381 censusTracing.newClientCallTracer(null, method.getFullMethodName());
382 Metadata headers = new Metadata();
383 ClientStreamTracer tracer = callTracer.newClientStreamTracer(headers);
384 verify(mockSpanFactory).startSpan(
Bogdan Drutu530b7142017-05-23 15:10:36 -0700385 isNull(Span.class), eq("Sent.package1.service2.method3"), argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700386 verify(spyClientSpan, never()).end(any(EndSpanOptions.class));
387
388 tracer.streamClosed(Status.OK);
389 callTracer.callEnded(Status.OK);
390
391 verify(spyClientSpan).end(
392 EndSpanOptions.builder().setStatus(com.google.instrumentation.trace.Status.OK).build());
393 verifyNoMoreInteractions(mockSpanFactory);
394 }
395
396 @Test
397 public void clientStreamNeverCreatedStillRecordStats() {
398 CensusStatsModule.ClientCallTracer callTracer =
399 censusStats.newClientCallTracer(
400 statsCtxFactory.getDefault(), method.getFullMethodName());
401
402 fakeClock.forwardTime(3000, MILLISECONDS);
403 callTracer.callEnded(Status.DEADLINE_EXCEEDED.withDescription("3 seconds"));
404
405 StatsTestUtils.MetricsRecord record = statsCtxFactory.pollRecord();
406 assertNotNull(record);
407 assertNoServerContent(record);
408 TagValue methodTag = record.tags.get(RpcConstants.RPC_CLIENT_METHOD);
409 assertEquals(method.getFullMethodName(), methodTag.toString());
410 TagValue statusTag = record.tags.get(RpcConstants.RPC_STATUS);
411 assertEquals(Status.Code.DEADLINE_EXCEEDED.toString(), statusTag.toString());
412 assertEquals(1, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_ERROR_COUNT));
413 assertEquals(0, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_REQUEST_BYTES));
414 assertEquals(0,
415 record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
416 assertEquals(0, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_RESPONSE_BYTES));
417 assertEquals(0,
418 record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
419 assertEquals(3000, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
420 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_SERVER_ELAPSED_TIME));
421 }
422
423 @Test
424 public void clientStreamNeverCreatedStillRecordTracing() {
425 CensusTracingModule.ClientCallTracer callTracer =
426 censusTracing.newClientCallTracer(fakeClientParentSpan, method.getFullMethodName());
427 verify(mockSpanFactory).startSpan(
428 same(fakeClientParentSpan), eq("Sent.package1.service2.method3"),
Bogdan Drutu530b7142017-05-23 15:10:36 -0700429 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700430
431 callTracer.callEnded(Status.DEADLINE_EXCEEDED.withDescription("3 seconds"));
432 verify(spyClientSpan).end(
433 EndSpanOptions.builder()
434 .setStatus(
435 com.google.instrumentation.trace.Status.DEADLINE_EXCEEDED
436 .withDescription("3 seconds"))
437 .build());
438 verify(spyClientSpan, never()).end();
439 }
440
441 @Test
442 public void statsHeadersPropagateTags() {
Kun Zhangbe74e972017-04-26 10:50:55 -0700443 subtestStatsHeadersPropagateTags(true);
444 }
445
446 @Test
447 public void statsHeadersNotPropagateTags() {
448 subtestStatsHeadersPropagateTags(false);
449 }
450
451 private void subtestStatsHeadersPropagateTags(boolean propagate) {
Kun Zhang49bde542017-04-25 13:53:29 -0700452 // EXTRA_TAG is propagated by the FakeStatsContextFactory. Note that not all tags are
453 // propagated. The StatsContextFactory decides which tags are to propagated. gRPC facilitates
454 // the propagation by putting them in the headers.
455 StatsContext clientCtx = statsCtxFactory.getDefault().with(
456 StatsTestUtils.EXTRA_TAG, TagValue.create("extra-tag-value-897"));
Kun Zhangbe74e972017-04-26 10:50:55 -0700457 CensusStatsModule census =
458 new CensusStatsModule(statsCtxFactory, fakeClock.getStopwatchSupplier(), propagate);
Kun Zhang49bde542017-04-25 13:53:29 -0700459 Metadata headers = new Metadata();
Kun Zhangbe74e972017-04-26 10:50:55 -0700460 CensusStatsModule.ClientCallTracer callTracer =
461 census.newClientCallTracer(clientCtx, method.getFullMethodName());
462 // This propagates clientCtx to headers if propagates==true
Kun Zhang49bde542017-04-25 13:53:29 -0700463 callTracer.newClientStreamTracer(headers);
Kun Zhangbe74e972017-04-26 10:50:55 -0700464 if (propagate) {
465 assertTrue(headers.containsKey(census.statsHeader));
466 } else {
467 assertFalse(headers.containsKey(census.statsHeader));
468 return;
469 }
Kun Zhang49bde542017-04-25 13:53:29 -0700470
471 ServerStreamTracer serverTracer =
Kun Zhangbe74e972017-04-26 10:50:55 -0700472 census.getServerTracerFactory().newServerStreamTracer(
Kun Zhang49bde542017-04-25 13:53:29 -0700473 method.getFullMethodName(), headers);
474 // Server tracer deserializes clientCtx from the headers, so that it records stats with the
475 // propagated tags.
476 Context serverContext = serverTracer.filterContext(Context.ROOT);
477 // It also put clientCtx in the Context seen by the call handler
478 assertEquals(clientCtx, CensusStatsModule.STATS_CONTEXT_KEY.get(serverContext));
479
Kun Zhang49bde542017-04-25 13:53:29 -0700480 // Verifies that the server tracer records the status with the propagated tag
481 serverTracer.streamClosed(Status.OK);
482
483 StatsTestUtils.MetricsRecord serverRecord = statsCtxFactory.pollRecord();
484 assertNotNull(serverRecord);
485 assertNoClientContent(serverRecord);
486 TagValue serverMethodTag = serverRecord.tags.get(RpcConstants.RPC_SERVER_METHOD);
487 assertEquals(method.getFullMethodName(), serverMethodTag.toString());
488 TagValue serverStatusTag = serverRecord.tags.get(RpcConstants.RPC_STATUS);
489 assertEquals(Status.Code.OK.toString(), serverStatusTag.toString());
490 assertNull(serverRecord.getMetric(RpcConstants.RPC_SERVER_ERROR_COUNT));
491 TagValue serverPropagatedTag = serverRecord.tags.get(StatsTestUtils.EXTRA_TAG);
492 assertEquals("extra-tag-value-897", serverPropagatedTag.toString());
493
494 // Verifies that the client tracer factory uses clientCtx, which includes the custom tags, to
495 // record stats.
496 callTracer.callEnded(Status.OK);
497
498 StatsTestUtils.MetricsRecord clientRecord = statsCtxFactory.pollRecord();
499 assertNotNull(clientRecord);
500 assertNoServerContent(clientRecord);
501 TagValue clientMethodTag = clientRecord.tags.get(RpcConstants.RPC_CLIENT_METHOD);
502 assertEquals(method.getFullMethodName(), clientMethodTag.toString());
503 TagValue clientStatusTag = clientRecord.tags.get(RpcConstants.RPC_STATUS);
504 assertEquals(Status.Code.OK.toString(), clientStatusTag.toString());
505 assertNull(clientRecord.getMetric(RpcConstants.RPC_CLIENT_ERROR_COUNT));
506 TagValue clientPropagatedTag = clientRecord.tags.get(StatsTestUtils.EXTRA_TAG);
507 assertEquals("extra-tag-value-897", clientPropagatedTag.toString());
508 }
509
510 @Test
511 public void statsHeadersNotPropagateDefaultContext() {
512 CensusStatsModule.ClientCallTracer callTracer =
513 censusStats.newClientCallTracer(statsCtxFactory.getDefault(), method.getFullMethodName());
514 Metadata headers = new Metadata();
515 callTracer.newClientStreamTracer(headers);
516 assertFalse(headers.containsKey(censusStats.statsHeader));
517 }
518
519 @Test
520 public void statsHeaderMalformed() {
521 // Construct a malformed header and make sure parsing it will throw
522 byte[] statsHeaderValue = new byte[]{1};
523 Metadata.Key<byte[]> arbitraryStatsHeader =
524 Metadata.Key.of("grpc-tags-bin", Metadata.BINARY_BYTE_MARSHALLER);
525 try {
526 statsCtxFactory.deserialize(new ByteArrayInputStream(statsHeaderValue));
527 fail("Should have thrown");
528 } catch (Exception e) {
529 // Expected
530 }
531
532 // But the header key will return a default context for it
533 Metadata headers = new Metadata();
534 assertNull(headers.get(censusStats.statsHeader));
535 headers.put(arbitraryStatsHeader, statsHeaderValue);
536 assertSame(statsCtxFactory.getDefault(), headers.get(censusStats.statsHeader));
537 }
538
539 @Test
540 public void traceHeadersPropagateSpanContext() throws Exception {
541 CensusTracingModule.ClientCallTracer callTracer =
542 censusTracing.newClientCallTracer(fakeClientParentSpan, method.getFullMethodName());
543 Metadata headers = new Metadata();
544 callTracer.newClientStreamTracer(headers);
545
546 verify(mockTracingPropagationHandler).toBinaryValue(same(fakeClientSpanContext));
547 verifyNoMoreInteractions(mockTracingPropagationHandler);
548 verify(mockSpanFactory).startSpan(
549 same(fakeClientParentSpan), eq("Sent.package1.service2.method3"),
Bogdan Drutu530b7142017-05-23 15:10:36 -0700550 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700551 verifyNoMoreInteractions(mockSpanFactory);
552 assertTrue(headers.containsKey(censusTracing.tracingHeader));
553
554 ServerStreamTracer serverTracer =
555 censusTracing.getServerTracerFactory().newServerStreamTracer(
556 method.getFullMethodName(), headers);
557 verify(mockTracingPropagationHandler).fromBinaryValue(same(binarySpanContext));
558 verify(mockSpanFactory).startSpanWithRemoteParent(
559 same(fakeServerParentSpanContext), eq("Recv.package1.service2.method3"),
Bogdan Drutu530b7142017-05-23 15:10:36 -0700560 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700561
562 Context filteredContext = serverTracer.filterContext(Context.ROOT);
563 assertSame(spyServerSpan, ContextUtils.CONTEXT_SPAN_KEY.get(filteredContext));
564 }
565
566 @Test
567 public void traceHeaderMalformed() throws Exception {
568 // As comparison, normal header parsing
569 Metadata headers = new Metadata();
570 headers.put(censusTracing.tracingHeader, fakeClientSpanContext);
571 // mockTracingPropagationHandler was stubbed to always return fakeServerParentSpanContext
572 assertSame(fakeServerParentSpanContext, headers.get(censusTracing.tracingHeader));
573
574 // Make BinaryPropagationHandler always throw when parsing the header
575 when(mockTracingPropagationHandler.fromBinaryValue(any(byte[].class)))
576 .thenThrow(new ParseException("Malformed header", 0));
577
578 headers = new Metadata();
579 assertNull(headers.get(censusTracing.tracingHeader));
580 headers.put(censusTracing.tracingHeader, fakeClientSpanContext);
581 assertSame(SpanContext.INVALID, headers.get(censusTracing.tracingHeader));
582 assertNotSame(fakeServerParentSpanContext, SpanContext.INVALID);
583
584 // A null Span is used as the parent in this case
585 censusTracing.getServerTracerFactory().newServerStreamTracer(
586 method.getFullMethodName(), headers);
587 verify(mockSpanFactory).startSpanWithRemoteParent(
588 isNull(SpanContext.class), eq("Recv.package1.service2.method3"),
Bogdan Drutu530b7142017-05-23 15:10:36 -0700589 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700590 }
591
592 @Test
593 public void serverBasicStatsNoHeaders() {
594 ServerStreamTracer.Factory tracerFactory = censusStats.getServerTracerFactory();
595 ServerStreamTracer tracer =
596 tracerFactory.newServerStreamTracer(method.getFullMethodName(), new Metadata());
597
598 Context filteredContext = tracer.filterContext(Context.ROOT);
599 assertNull(CensusStatsModule.STATS_CONTEXT_KEY.get(filteredContext));
600
601 tracer.inboundWireSize(34);
602 tracer.inboundUncompressedSize(67);
603
604 fakeClock.forwardTime(100, MILLISECONDS);
605 tracer.outboundWireSize(1028);
606 tracer.outboundUncompressedSize(1128);
607
608 fakeClock.forwardTime(16, MILLISECONDS);
609 tracer.inboundWireSize(154);
610 tracer.inboundUncompressedSize(552);
611 tracer.outboundWireSize(99);
612 tracer.outboundUncompressedSize(865);
613
614 fakeClock.forwardTime(24, MILLISECONDS);
615
616 tracer.streamClosed(Status.CANCELLED);
617
618 StatsTestUtils.MetricsRecord record = statsCtxFactory.pollRecord();
619 assertNotNull(record);
620 assertNoClientContent(record);
621 TagValue methodTag = record.tags.get(RpcConstants.RPC_SERVER_METHOD);
622 assertEquals(method.getFullMethodName(), methodTag.toString());
623 TagValue statusTag = record.tags.get(RpcConstants.RPC_STATUS);
624 assertEquals(Status.Code.CANCELLED.toString(), statusTag.toString());
625 assertEquals(1, record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_ERROR_COUNT));
626 assertEquals(1028 + 99, record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_RESPONSE_BYTES));
627 assertEquals(1128 + 865,
628 record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES));
629 assertEquals(34 + 154, record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_REQUEST_BYTES));
630 assertEquals(67 + 552,
631 record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES));
632 assertEquals(100 + 16 + 24,
633 record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_SERVER_LATENCY));
634 }
635
636 @Test
637 public void serverBasicTracingNoHeaders() {
638 ServerStreamTracer.Factory tracerFactory = censusTracing.getServerTracerFactory();
639 ServerStreamTracer tracer =
640 tracerFactory.newServerStreamTracer(method.getFullMethodName(), new Metadata());
641 verifyZeroInteractions(mockTracingPropagationHandler);
642 verify(mockSpanFactory).startSpanWithRemoteParent(
643 isNull(SpanContext.class), eq("Recv.package1.service2.method3"),
Bogdan Drutu530b7142017-05-23 15:10:36 -0700644 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700645
646 Context filteredContext = tracer.filterContext(Context.ROOT);
647 assertSame(spyServerSpan, ContextUtils.CONTEXT_SPAN_KEY.get(filteredContext));
648
649 verify(spyServerSpan, never()).end(any(EndSpanOptions.class));
650 tracer.streamClosed(Status.CANCELLED);
651
652 verify(spyServerSpan).end(
653 EndSpanOptions.builder()
654 .setStatus(com.google.instrumentation.trace.Status.CANCELLED).build());
655 verify(spyServerSpan, never()).end();
656 }
657
658 @Test
659 public void convertToTracingStatus() {
660 // Without description
661 for (Status.Code grpcCode : Status.Code.values()) {
662 Status grpcStatus = Status.fromCode(grpcCode);
663 com.google.instrumentation.trace.Status tracingStatus =
664 CensusTracingModule.convertStatus(grpcStatus);
665 assertEquals(grpcCode.toString(), tracingStatus.getCanonicalCode().toString());
666 assertNull(tracingStatus.getDescription());
667 }
668
669 // With description
670 for (Status.Code grpcCode : Status.Code.values()) {
671 Status grpcStatus = Status.fromCode(grpcCode).withDescription("This is my description");
672 com.google.instrumentation.trace.Status tracingStatus =
673 CensusTracingModule.convertStatus(grpcStatus);
674 assertEquals(grpcCode.toString(), tracingStatus.getCanonicalCode().toString());
675 assertEquals(grpcStatus.getDescription(), tracingStatus.getDescription());
676 }
677 }
678
679 private static void assertNoServerContent(StatsTestUtils.MetricsRecord record) {
680 assertNull(record.getMetric(RpcConstants.RPC_SERVER_ERROR_COUNT));
681 assertNull(record.getMetric(RpcConstants.RPC_SERVER_REQUEST_BYTES));
682 assertNull(record.getMetric(RpcConstants.RPC_SERVER_RESPONSE_BYTES));
683 assertNull(record.getMetric(RpcConstants.RPC_SERVER_SERVER_ELAPSED_TIME));
684 assertNull(record.getMetric(RpcConstants.RPC_SERVER_SERVER_LATENCY));
685 assertNull(record.getMetric(RpcConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES));
686 assertNull(record.getMetric(RpcConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES));
687 }
688
689 private static void assertNoClientContent(StatsTestUtils.MetricsRecord record) {
690 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_ERROR_COUNT));
691 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_REQUEST_BYTES));
692 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_RESPONSE_BYTES));
693 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
694 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_SERVER_ELAPSED_TIME));
695 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
696 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
697 }
698
699 // Promote the visibility of SpanFactory's methods to allow mocking
700 private abstract static class AccessibleSpanFactory extends SpanFactory {
701 @Override
702 public abstract Span startSpan(@Nullable Span parent, String name, StartSpanOptions options);
703
704 @Override
705 public abstract Span startSpanWithRemoteParent(
706 @Nullable SpanContext remoteParent, String name, StartSpanOptions options);
707 }
708
709 private static class FakeSpan extends Span {
710 FakeSpan(SpanContext ctx) {
711 super(ctx, null);
712 }
713
714 @Override
715 public void addAttributes(Map<String, AttributeValue> attributes) {
716 }
717
718 @Override
719 public void addAnnotation(String description, Map<String, AttributeValue> attributes) {
720 }
721
722 @Override
723 public void addAnnotation(Annotation annotation) {
724 }
725
726 @Override
727 public void addNetworkEvent(NetworkEvent networkEvent) {
728 }
729
730 @Override
731 public void addLink(Link link) {
732 }
733
734 @Override
735 public void end(EndSpanOptions options) {
736 }
737 }
738}