blob: 466b51f4cd79c184bd6efcdaefc717be890bfa2e [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
Łukasz Strzałkowski17af7562017-06-09 13:53:39 -0700134 private final MethodDescriptor<String, String> method =
135 MethodDescriptor.<String, String>newBuilder()
136 .setType(MethodDescriptor.MethodType.UNKNOWN)
137 .setRequestMarshaller(MARSHALLER)
138 .setResponseMarshaller(MARSHALLER)
139 .setFullMethodName("package1.service2/method3")
140 .build();
Kun Zhang49bde542017-04-25 13:53:29 -0700141 private final FakeClock fakeClock = new FakeClock();
142 private final FakeStatsContextFactory statsCtxFactory = new FakeStatsContextFactory();
143 private final Random random = new Random(0);
144 private final SpanContext fakeClientSpanContext =
145 SpanContext.create(
146 TraceId.generateRandomId(random), SpanId.generateRandomId(random),
147 TraceOptions.builder().build());
148 private final SpanContext fakeClientParentSpanContext =
149 SpanContext.create(
150 TraceId.generateRandomId(random), SpanId.generateRandomId(random),
151 TraceOptions.builder().build());
152 private final SpanContext fakeServerSpanContext =
153 SpanContext.create(
154 TraceId.generateRandomId(random), SpanId.generateRandomId(random),
155 TraceOptions.builder().build());
156 private final SpanContext fakeServerParentSpanContext =
157 SpanContext.create(
158 TraceId.generateRandomId(random), SpanId.generateRandomId(random),
159 TraceOptions.builder().build());
160 private final Span fakeClientSpan = new FakeSpan(fakeClientSpanContext);
161 private final Span fakeServerSpan = new FakeSpan(fakeServerSpanContext);
162 private final Span fakeClientParentSpan = new FakeSpan(fakeClientParentSpanContext);
163 private final Span fakeServerParentSpan = new FakeSpan(fakeServerParentSpanContext);
164 private final Span spyClientSpan = spy(fakeClientSpan);
165 private final Span spyServerSpan = spy(fakeServerSpan);
166 private final byte[] binarySpanContext = new byte[]{3, 1, 5};
Bogdan Drutu530b7142017-05-23 15:10:36 -0700167 private final ArgumentMatcher<StartSpanOptions> startSpanOptionsMatcher =
168 new ArgumentMatcher<StartSpanOptions>() {
169 @Override
170 public boolean matches(Object argument) {
171 return Boolean.TRUE.equals(((StartSpanOptions) argument).getRecordEvents());
172 }
173 };
Kun Zhang49bde542017-04-25 13:53:29 -0700174
175 @Rule
176 public final GrpcServerRule grpcServerRule = new GrpcServerRule().directExecutor();
177
178 @Mock
179 private AccessibleSpanFactory mockSpanFactory;
180 @Mock
181 private BinaryPropagationHandler mockTracingPropagationHandler;
182 @Mock
183 private ClientCall.Listener<String> mockClientCallListener;
184 @Mock
185 private ServerCall.Listener<String> mockServerCallListener;
186 @Captor
187 private ArgumentCaptor<CallOptions> callOptionsCaptor;
188 @Captor
189 private ArgumentCaptor<ClientCall.Listener<String>> clientCallListenerCaptor;
190 @Captor
191 private ArgumentCaptor<Status> statusCaptor;
192
193 private Tracer tracer;
194 private CensusStatsModule censusStats;
195 private CensusTracingModule censusTracing;
196
197 @Before
198 @SuppressWarnings("unchecked")
199 public void setUp() throws Exception {
200 MockitoAnnotations.initMocks(this);
201 when(mockSpanFactory.startSpan(any(Span.class), anyString(), any(StartSpanOptions.class)))
202 .thenReturn(spyClientSpan);
203 when(
204 mockSpanFactory.startSpanWithRemoteParent(
205 any(SpanContext.class), anyString(), any(StartSpanOptions.class)))
206 .thenReturn(spyServerSpan);
207 when(mockTracingPropagationHandler.toBinaryValue(any(SpanContext.class)))
208 .thenReturn(binarySpanContext);
209 when(mockTracingPropagationHandler.fromBinaryValue(any(byte[].class)))
210 .thenReturn(fakeServerParentSpanContext);
211 tracer = new Tracer(mockSpanFactory) {};
Kun Zhangbe74e972017-04-26 10:50:55 -0700212 censusStats = new CensusStatsModule(statsCtxFactory, fakeClock.getStopwatchSupplier(), true);
Kun Zhang49bde542017-04-25 13:53:29 -0700213 censusTracing = new CensusTracingModule(tracer, mockTracingPropagationHandler);
214 }
215
216 @After
217 public void wrapUp() {
218 assertNull(statsCtxFactory.pollRecord());
219 }
220
221 @Test
222 public void clientInterceptorNoCustomTag() {
223 testClientInterceptors(false);
224 }
225
226 @Test
227 public void clientInterceptorCustomTag() {
228 testClientInterceptors(true);
229 }
230
231 // Test that Census ClientInterceptors uses the StatsContext and Span out of the current Context
232 // to create the ClientCallTracer, and that it intercepts ClientCall.Listener.onClose() to call
233 // ClientCallTracer.callEnded().
234 private void testClientInterceptors(boolean nonDefaultContext) {
235 grpcServerRule.getServiceRegistry().addService(
236 ServerServiceDefinition.builder("package1.service2").addMethod(
237 method, new ServerCallHandler<String, String>() {
238 @Override
239 public ServerCall.Listener<String> startCall(
240 ServerCall<String, String> call, Metadata headers) {
241 call.sendHeaders(new Metadata());
242 call.sendMessage("Hello");
243 call.close(
244 Status.PERMISSION_DENIED.withDescription("No you don't"), new Metadata());
245 return mockServerCallListener;
246 }
247 }).build());
248
249 final AtomicReference<CallOptions> capturedCallOptions = new AtomicReference<CallOptions>();
250 ClientInterceptor callOptionsCaptureInterceptor = new ClientInterceptor() {
251 @Override
252 public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
253 MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
254 capturedCallOptions.set(callOptions);
255 return next.newCall(method, callOptions);
256 }
257 };
258 Channel interceptedChannel =
259 ClientInterceptors.intercept(
260 grpcServerRule.getChannel(), callOptionsCaptureInterceptor,
261 censusStats.getClientInterceptor(), censusTracing.getClientInterceptor());
262 ClientCall<String, String> call;
263 if (nonDefaultContext) {
264 Context ctx =
265 Context.ROOT.withValues(
Yang Song4a0cf0b2017-06-06 18:16:55 -0700266 STATS_CONTEXT_KEY,
Kun Zhang49bde542017-04-25 13:53:29 -0700267 statsCtxFactory.getDefault().with(
268 StatsTestUtils.EXTRA_TAG, TagValue.create("extra value")),
269 ContextUtils.CONTEXT_SPAN_KEY,
270 fakeClientParentSpan);
271 Context origCtx = ctx.attach();
272 try {
273 call = interceptedChannel.newCall(method, CALL_OPTIONS);
274 } finally {
275 ctx.detach(origCtx);
276 }
277 } else {
Yang Song4a0cf0b2017-06-06 18:16:55 -0700278 assertNull(STATS_CONTEXT_KEY.get());
Kun Zhang49bde542017-04-25 13:53:29 -0700279 assertNull(ContextUtils.CONTEXT_SPAN_KEY.get());
280 call = interceptedChannel.newCall(method, CALL_OPTIONS);
281 }
282
283 // The interceptor adds tracer factory to CallOptions
284 assertEquals("customvalue", capturedCallOptions.get().getOption(CUSTOM_OPTION));
285 assertEquals(2, capturedCallOptions.get().getStreamTracerFactories().size());
286 assertTrue(
287 capturedCallOptions.get().getStreamTracerFactories().get(0)
288 instanceof CensusTracingModule.ClientCallTracer);
289 assertTrue(
290 capturedCallOptions.get().getStreamTracerFactories().get(1)
291 instanceof CensusStatsModule.ClientCallTracer);
292
293 // Make the call
294 Metadata headers = new Metadata();
295 call.start(mockClientCallListener, headers);
296 assertNull(statsCtxFactory.pollRecord());
297 if (nonDefaultContext) {
298 verify(mockSpanFactory).startSpan(
299 same(fakeClientParentSpan), eq("Sent.package1.service2.method3"),
Bogdan Drutu530b7142017-05-23 15:10:36 -0700300 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700301 } else {
302 verify(mockSpanFactory).startSpan(
Bogdan Drutu530b7142017-05-23 15:10:36 -0700303 isNull(Span.class), eq("Sent.package1.service2.method3"),
304 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700305 }
306 verify(spyClientSpan, never()).end(any(EndSpanOptions.class));
307
308 // End the call
309 call.halfClose();
310 call.request(1);
311
312 verify(mockClientCallListener).onClose(statusCaptor.capture(), any(Metadata.class));
313 Status status = statusCaptor.getValue();
314 assertEquals(Status.Code.PERMISSION_DENIED, status.getCode());
315 assertEquals("No you don't", status.getDescription());
316
317 // The intercepting listener calls callEnded() on ClientCallTracer, which records to Census.
318 StatsTestUtils.MetricsRecord record = statsCtxFactory.pollRecord();
319 assertNotNull(record);
320 TagValue methodTag = record.tags.get(RpcConstants.RPC_CLIENT_METHOD);
321 assertEquals(method.getFullMethodName(), methodTag.toString());
322 TagValue statusTag = record.tags.get(RpcConstants.RPC_STATUS);
323 assertEquals(Status.Code.PERMISSION_DENIED.toString(), statusTag.toString());
324 if (nonDefaultContext) {
325 TagValue extraTag = record.tags.get(StatsTestUtils.EXTRA_TAG);
326 assertEquals("extra value", extraTag.toString());
327 } else {
328 assertNull(record.tags.get(StatsTestUtils.EXTRA_TAG));
329 }
330 verify(spyClientSpan).end(
331 EndSpanOptions.builder()
332 .setStatus(
333 com.google.instrumentation.trace.Status.PERMISSION_DENIED
334 .withDescription("No you don't"))
335 .build());
336 verify(spyClientSpan, never()).end();
337 }
338
339 @Test
340 public void clientBasicStatsDefaultContext() {
341 CensusStatsModule.ClientCallTracer callTracer =
342 censusStats.newClientCallTracer(statsCtxFactory.getDefault(), method.getFullMethodName());
343 Metadata headers = new Metadata();
344 ClientStreamTracer tracer = callTracer.newClientStreamTracer(headers);
345
346 fakeClock.forwardTime(30, MILLISECONDS);
347 tracer.outboundHeaders();
348
349 fakeClock.forwardTime(100, MILLISECONDS);
350 tracer.outboundWireSize(1028);
351 tracer.outboundUncompressedSize(1128);
352
353 fakeClock.forwardTime(16, MILLISECONDS);
354 tracer.inboundWireSize(33);
355 tracer.inboundUncompressedSize(67);
356 tracer.outboundWireSize(99);
357 tracer.outboundUncompressedSize(865);
358
359 fakeClock.forwardTime(24, MILLISECONDS);
360 tracer.inboundWireSize(154);
361 tracer.inboundUncompressedSize(552);
362 tracer.streamClosed(Status.OK);
363 callTracer.callEnded(Status.OK);
364
365 StatsTestUtils.MetricsRecord record = statsCtxFactory.pollRecord();
366 assertNotNull(record);
367 assertNoServerContent(record);
368 TagValue methodTag = record.tags.get(RpcConstants.RPC_CLIENT_METHOD);
369 assertEquals(method.getFullMethodName(), methodTag.toString());
370 TagValue statusTag = record.tags.get(RpcConstants.RPC_STATUS);
371 assertEquals(Status.Code.OK.toString(), statusTag.toString());
372 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_ERROR_COUNT));
373 assertEquals(1028 + 99, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_REQUEST_BYTES));
374 assertEquals(1128 + 865,
375 record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
376 assertEquals(33 + 154, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_RESPONSE_BYTES));
377 assertEquals(67 + 552,
378 record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
379 assertEquals(30 + 100 + 16 + 24,
380 record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
381 }
382
383 @Test
384 public void clientBasicTracingDefaultSpan() {
385 CensusTracingModule.ClientCallTracer callTracer =
386 censusTracing.newClientCallTracer(null, method.getFullMethodName());
387 Metadata headers = new Metadata();
388 ClientStreamTracer tracer = callTracer.newClientStreamTracer(headers);
389 verify(mockSpanFactory).startSpan(
Bogdan Drutu530b7142017-05-23 15:10:36 -0700390 isNull(Span.class), eq("Sent.package1.service2.method3"), argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700391 verify(spyClientSpan, never()).end(any(EndSpanOptions.class));
392
393 tracer.streamClosed(Status.OK);
394 callTracer.callEnded(Status.OK);
395
396 verify(spyClientSpan).end(
397 EndSpanOptions.builder().setStatus(com.google.instrumentation.trace.Status.OK).build());
398 verifyNoMoreInteractions(mockSpanFactory);
399 }
400
401 @Test
402 public void clientStreamNeverCreatedStillRecordStats() {
403 CensusStatsModule.ClientCallTracer callTracer =
404 censusStats.newClientCallTracer(
405 statsCtxFactory.getDefault(), method.getFullMethodName());
406
407 fakeClock.forwardTime(3000, MILLISECONDS);
408 callTracer.callEnded(Status.DEADLINE_EXCEEDED.withDescription("3 seconds"));
409
410 StatsTestUtils.MetricsRecord record = statsCtxFactory.pollRecord();
411 assertNotNull(record);
412 assertNoServerContent(record);
413 TagValue methodTag = record.tags.get(RpcConstants.RPC_CLIENT_METHOD);
414 assertEquals(method.getFullMethodName(), methodTag.toString());
415 TagValue statusTag = record.tags.get(RpcConstants.RPC_STATUS);
416 assertEquals(Status.Code.DEADLINE_EXCEEDED.toString(), statusTag.toString());
417 assertEquals(1, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_ERROR_COUNT));
418 assertEquals(0, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_REQUEST_BYTES));
419 assertEquals(0,
420 record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
421 assertEquals(0, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_RESPONSE_BYTES));
422 assertEquals(0,
423 record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
424 assertEquals(3000, record.getMetricAsLongOrFail(RpcConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
425 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_SERVER_ELAPSED_TIME));
426 }
427
428 @Test
429 public void clientStreamNeverCreatedStillRecordTracing() {
430 CensusTracingModule.ClientCallTracer callTracer =
431 censusTracing.newClientCallTracer(fakeClientParentSpan, method.getFullMethodName());
432 verify(mockSpanFactory).startSpan(
433 same(fakeClientParentSpan), eq("Sent.package1.service2.method3"),
Bogdan Drutu530b7142017-05-23 15:10:36 -0700434 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700435
436 callTracer.callEnded(Status.DEADLINE_EXCEEDED.withDescription("3 seconds"));
437 verify(spyClientSpan).end(
438 EndSpanOptions.builder()
439 .setStatus(
440 com.google.instrumentation.trace.Status.DEADLINE_EXCEEDED
441 .withDescription("3 seconds"))
442 .build());
443 verify(spyClientSpan, never()).end();
444 }
445
446 @Test
447 public void statsHeadersPropagateTags() {
Kun Zhangbe74e972017-04-26 10:50:55 -0700448 subtestStatsHeadersPropagateTags(true);
449 }
450
451 @Test
452 public void statsHeadersNotPropagateTags() {
453 subtestStatsHeadersPropagateTags(false);
454 }
455
456 private void subtestStatsHeadersPropagateTags(boolean propagate) {
Kun Zhang49bde542017-04-25 13:53:29 -0700457 // EXTRA_TAG is propagated by the FakeStatsContextFactory. Note that not all tags are
458 // propagated. The StatsContextFactory decides which tags are to propagated. gRPC facilitates
459 // the propagation by putting them in the headers.
460 StatsContext clientCtx = statsCtxFactory.getDefault().with(
461 StatsTestUtils.EXTRA_TAG, TagValue.create("extra-tag-value-897"));
Kun Zhangbe74e972017-04-26 10:50:55 -0700462 CensusStatsModule census =
463 new CensusStatsModule(statsCtxFactory, fakeClock.getStopwatchSupplier(), propagate);
Kun Zhang49bde542017-04-25 13:53:29 -0700464 Metadata headers = new Metadata();
Kun Zhangbe74e972017-04-26 10:50:55 -0700465 CensusStatsModule.ClientCallTracer callTracer =
466 census.newClientCallTracer(clientCtx, method.getFullMethodName());
467 // This propagates clientCtx to headers if propagates==true
Kun Zhang49bde542017-04-25 13:53:29 -0700468 callTracer.newClientStreamTracer(headers);
Kun Zhangbe74e972017-04-26 10:50:55 -0700469 if (propagate) {
470 assertTrue(headers.containsKey(census.statsHeader));
471 } else {
472 assertFalse(headers.containsKey(census.statsHeader));
473 return;
474 }
Kun Zhang49bde542017-04-25 13:53:29 -0700475
476 ServerStreamTracer serverTracer =
Kun Zhangbe74e972017-04-26 10:50:55 -0700477 census.getServerTracerFactory().newServerStreamTracer(
Kun Zhang49bde542017-04-25 13:53:29 -0700478 method.getFullMethodName(), headers);
479 // Server tracer deserializes clientCtx from the headers, so that it records stats with the
480 // propagated tags.
481 Context serverContext = serverTracer.filterContext(Context.ROOT);
482 // It also put clientCtx in the Context seen by the call handler
Yang Song4a0cf0b2017-06-06 18:16:55 -0700483 assertEquals(clientCtx, STATS_CONTEXT_KEY.get(serverContext));
Kun Zhang49bde542017-04-25 13:53:29 -0700484
Kun Zhang49bde542017-04-25 13:53:29 -0700485 // Verifies that the server tracer records the status with the propagated tag
486 serverTracer.streamClosed(Status.OK);
487
488 StatsTestUtils.MetricsRecord serverRecord = statsCtxFactory.pollRecord();
489 assertNotNull(serverRecord);
490 assertNoClientContent(serverRecord);
491 TagValue serverMethodTag = serverRecord.tags.get(RpcConstants.RPC_SERVER_METHOD);
492 assertEquals(method.getFullMethodName(), serverMethodTag.toString());
493 TagValue serverStatusTag = serverRecord.tags.get(RpcConstants.RPC_STATUS);
494 assertEquals(Status.Code.OK.toString(), serverStatusTag.toString());
495 assertNull(serverRecord.getMetric(RpcConstants.RPC_SERVER_ERROR_COUNT));
496 TagValue serverPropagatedTag = serverRecord.tags.get(StatsTestUtils.EXTRA_TAG);
497 assertEquals("extra-tag-value-897", serverPropagatedTag.toString());
498
499 // Verifies that the client tracer factory uses clientCtx, which includes the custom tags, to
500 // record stats.
501 callTracer.callEnded(Status.OK);
502
503 StatsTestUtils.MetricsRecord clientRecord = statsCtxFactory.pollRecord();
504 assertNotNull(clientRecord);
505 assertNoServerContent(clientRecord);
506 TagValue clientMethodTag = clientRecord.tags.get(RpcConstants.RPC_CLIENT_METHOD);
507 assertEquals(method.getFullMethodName(), clientMethodTag.toString());
508 TagValue clientStatusTag = clientRecord.tags.get(RpcConstants.RPC_STATUS);
509 assertEquals(Status.Code.OK.toString(), clientStatusTag.toString());
510 assertNull(clientRecord.getMetric(RpcConstants.RPC_CLIENT_ERROR_COUNT));
511 TagValue clientPropagatedTag = clientRecord.tags.get(StatsTestUtils.EXTRA_TAG);
512 assertEquals("extra-tag-value-897", clientPropagatedTag.toString());
513 }
514
515 @Test
516 public void statsHeadersNotPropagateDefaultContext() {
517 CensusStatsModule.ClientCallTracer callTracer =
518 censusStats.newClientCallTracer(statsCtxFactory.getDefault(), method.getFullMethodName());
519 Metadata headers = new Metadata();
520 callTracer.newClientStreamTracer(headers);
521 assertFalse(headers.containsKey(censusStats.statsHeader));
522 }
523
524 @Test
525 public void statsHeaderMalformed() {
526 // Construct a malformed header and make sure parsing it will throw
527 byte[] statsHeaderValue = new byte[]{1};
528 Metadata.Key<byte[]> arbitraryStatsHeader =
529 Metadata.Key.of("grpc-tags-bin", Metadata.BINARY_BYTE_MARSHALLER);
530 try {
531 statsCtxFactory.deserialize(new ByteArrayInputStream(statsHeaderValue));
532 fail("Should have thrown");
533 } catch (Exception e) {
534 // Expected
535 }
536
537 // But the header key will return a default context for it
538 Metadata headers = new Metadata();
539 assertNull(headers.get(censusStats.statsHeader));
540 headers.put(arbitraryStatsHeader, statsHeaderValue);
541 assertSame(statsCtxFactory.getDefault(), headers.get(censusStats.statsHeader));
542 }
543
544 @Test
545 public void traceHeadersPropagateSpanContext() throws Exception {
546 CensusTracingModule.ClientCallTracer callTracer =
547 censusTracing.newClientCallTracer(fakeClientParentSpan, method.getFullMethodName());
548 Metadata headers = new Metadata();
549 callTracer.newClientStreamTracer(headers);
550
551 verify(mockTracingPropagationHandler).toBinaryValue(same(fakeClientSpanContext));
552 verifyNoMoreInteractions(mockTracingPropagationHandler);
553 verify(mockSpanFactory).startSpan(
554 same(fakeClientParentSpan), eq("Sent.package1.service2.method3"),
Bogdan Drutu530b7142017-05-23 15:10:36 -0700555 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700556 verifyNoMoreInteractions(mockSpanFactory);
557 assertTrue(headers.containsKey(censusTracing.tracingHeader));
558
559 ServerStreamTracer serverTracer =
560 censusTracing.getServerTracerFactory().newServerStreamTracer(
561 method.getFullMethodName(), headers);
562 verify(mockTracingPropagationHandler).fromBinaryValue(same(binarySpanContext));
563 verify(mockSpanFactory).startSpanWithRemoteParent(
564 same(fakeServerParentSpanContext), eq("Recv.package1.service2.method3"),
Bogdan Drutu530b7142017-05-23 15:10:36 -0700565 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700566
567 Context filteredContext = serverTracer.filterContext(Context.ROOT);
568 assertSame(spyServerSpan, ContextUtils.CONTEXT_SPAN_KEY.get(filteredContext));
569 }
570
571 @Test
572 public void traceHeaderMalformed() throws Exception {
573 // As comparison, normal header parsing
574 Metadata headers = new Metadata();
575 headers.put(censusTracing.tracingHeader, fakeClientSpanContext);
576 // mockTracingPropagationHandler was stubbed to always return fakeServerParentSpanContext
577 assertSame(fakeServerParentSpanContext, headers.get(censusTracing.tracingHeader));
578
579 // Make BinaryPropagationHandler always throw when parsing the header
580 when(mockTracingPropagationHandler.fromBinaryValue(any(byte[].class)))
581 .thenThrow(new ParseException("Malformed header", 0));
582
583 headers = new Metadata();
584 assertNull(headers.get(censusTracing.tracingHeader));
585 headers.put(censusTracing.tracingHeader, fakeClientSpanContext);
586 assertSame(SpanContext.INVALID, headers.get(censusTracing.tracingHeader));
587 assertNotSame(fakeServerParentSpanContext, SpanContext.INVALID);
588
589 // A null Span is used as the parent in this case
590 censusTracing.getServerTracerFactory().newServerStreamTracer(
591 method.getFullMethodName(), headers);
592 verify(mockSpanFactory).startSpanWithRemoteParent(
593 isNull(SpanContext.class), eq("Recv.package1.service2.method3"),
Bogdan Drutu530b7142017-05-23 15:10:36 -0700594 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700595 }
596
597 @Test
598 public void serverBasicStatsNoHeaders() {
599 ServerStreamTracer.Factory tracerFactory = censusStats.getServerTracerFactory();
600 ServerStreamTracer tracer =
601 tracerFactory.newServerStreamTracer(method.getFullMethodName(), new Metadata());
602
603 Context filteredContext = tracer.filterContext(Context.ROOT);
Yang Song4a0cf0b2017-06-06 18:16:55 -0700604 assertNull(STATS_CONTEXT_KEY.get(filteredContext));
Kun Zhang49bde542017-04-25 13:53:29 -0700605
606 tracer.inboundWireSize(34);
607 tracer.inboundUncompressedSize(67);
608
609 fakeClock.forwardTime(100, MILLISECONDS);
610 tracer.outboundWireSize(1028);
611 tracer.outboundUncompressedSize(1128);
612
613 fakeClock.forwardTime(16, MILLISECONDS);
614 tracer.inboundWireSize(154);
615 tracer.inboundUncompressedSize(552);
616 tracer.outboundWireSize(99);
617 tracer.outboundUncompressedSize(865);
618
619 fakeClock.forwardTime(24, MILLISECONDS);
620
621 tracer.streamClosed(Status.CANCELLED);
622
623 StatsTestUtils.MetricsRecord record = statsCtxFactory.pollRecord();
624 assertNotNull(record);
625 assertNoClientContent(record);
626 TagValue methodTag = record.tags.get(RpcConstants.RPC_SERVER_METHOD);
627 assertEquals(method.getFullMethodName(), methodTag.toString());
628 TagValue statusTag = record.tags.get(RpcConstants.RPC_STATUS);
629 assertEquals(Status.Code.CANCELLED.toString(), statusTag.toString());
630 assertEquals(1, record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_ERROR_COUNT));
631 assertEquals(1028 + 99, record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_RESPONSE_BYTES));
632 assertEquals(1128 + 865,
633 record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES));
634 assertEquals(34 + 154, record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_REQUEST_BYTES));
635 assertEquals(67 + 552,
636 record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES));
637 assertEquals(100 + 16 + 24,
638 record.getMetricAsLongOrFail(RpcConstants.RPC_SERVER_SERVER_LATENCY));
639 }
640
641 @Test
642 public void serverBasicTracingNoHeaders() {
643 ServerStreamTracer.Factory tracerFactory = censusTracing.getServerTracerFactory();
644 ServerStreamTracer tracer =
645 tracerFactory.newServerStreamTracer(method.getFullMethodName(), new Metadata());
646 verifyZeroInteractions(mockTracingPropagationHandler);
647 verify(mockSpanFactory).startSpanWithRemoteParent(
648 isNull(SpanContext.class), eq("Recv.package1.service2.method3"),
Bogdan Drutu530b7142017-05-23 15:10:36 -0700649 argThat(startSpanOptionsMatcher));
Kun Zhang49bde542017-04-25 13:53:29 -0700650
651 Context filteredContext = tracer.filterContext(Context.ROOT);
652 assertSame(spyServerSpan, ContextUtils.CONTEXT_SPAN_KEY.get(filteredContext));
653
654 verify(spyServerSpan, never()).end(any(EndSpanOptions.class));
655 tracer.streamClosed(Status.CANCELLED);
656
657 verify(spyServerSpan).end(
658 EndSpanOptions.builder()
659 .setStatus(com.google.instrumentation.trace.Status.CANCELLED).build());
660 verify(spyServerSpan, never()).end();
661 }
662
663 @Test
664 public void convertToTracingStatus() {
665 // Without description
666 for (Status.Code grpcCode : Status.Code.values()) {
667 Status grpcStatus = Status.fromCode(grpcCode);
668 com.google.instrumentation.trace.Status tracingStatus =
669 CensusTracingModule.convertStatus(grpcStatus);
670 assertEquals(grpcCode.toString(), tracingStatus.getCanonicalCode().toString());
671 assertNull(tracingStatus.getDescription());
672 }
673
674 // With description
675 for (Status.Code grpcCode : Status.Code.values()) {
676 Status grpcStatus = Status.fromCode(grpcCode).withDescription("This is my description");
677 com.google.instrumentation.trace.Status tracingStatus =
678 CensusTracingModule.convertStatus(grpcStatus);
679 assertEquals(grpcCode.toString(), tracingStatus.getCanonicalCode().toString());
680 assertEquals(grpcStatus.getDescription(), tracingStatus.getDescription());
681 }
682 }
683
684 private static void assertNoServerContent(StatsTestUtils.MetricsRecord record) {
685 assertNull(record.getMetric(RpcConstants.RPC_SERVER_ERROR_COUNT));
686 assertNull(record.getMetric(RpcConstants.RPC_SERVER_REQUEST_BYTES));
687 assertNull(record.getMetric(RpcConstants.RPC_SERVER_RESPONSE_BYTES));
688 assertNull(record.getMetric(RpcConstants.RPC_SERVER_SERVER_ELAPSED_TIME));
689 assertNull(record.getMetric(RpcConstants.RPC_SERVER_SERVER_LATENCY));
690 assertNull(record.getMetric(RpcConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES));
691 assertNull(record.getMetric(RpcConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES));
692 }
693
694 private static void assertNoClientContent(StatsTestUtils.MetricsRecord record) {
695 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_ERROR_COUNT));
696 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_REQUEST_BYTES));
697 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_RESPONSE_BYTES));
698 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
699 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_SERVER_ELAPSED_TIME));
700 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
701 assertNull(record.getMetric(RpcConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
702 }
703
704 // Promote the visibility of SpanFactory's methods to allow mocking
705 private abstract static class AccessibleSpanFactory extends SpanFactory {
706 @Override
707 public abstract Span startSpan(@Nullable Span parent, String name, StartSpanOptions options);
708
709 @Override
710 public abstract Span startSpanWithRemoteParent(
711 @Nullable SpanContext remoteParent, String name, StartSpanOptions options);
712 }
713
714 private static class FakeSpan extends Span {
715 FakeSpan(SpanContext ctx) {
716 super(ctx, null);
717 }
718
719 @Override
720 public void addAttributes(Map<String, AttributeValue> attributes) {
721 }
722
723 @Override
724 public void addAnnotation(String description, Map<String, AttributeValue> attributes) {
725 }
726
727 @Override
728 public void addAnnotation(Annotation annotation) {
729 }
730
731 @Override
732 public void addNetworkEvent(NetworkEvent networkEvent) {
733 }
734
735 @Override
736 public void addLink(Link link) {
737 }
738
739 @Override
740 public void end(EndSpanOptions options) {
741 }
742 }
743}