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