@@ -379,4 +379,305 @@ public String getHeaderValue(int index) {
379379 .isEqualTo ("503" );
380380 }
381381 }
382+
383+ @ Test
384+ void testMetrics_zeroDeadline_grpc () throws Exception {
385+ GoldenSignalsMetricsTracerFactory tracerFactory =
386+ new GoldenSignalsMetricsTracerFactory (openTelemetrySdk );
387+
388+ // Using 1ms as 0ms might be rejected by some validation or trigger immediate failure before
389+ // metrics
390+ RetrySettings zeroRetrySettings =
391+ RetrySettings .newBuilder ()
392+ .setInitialRpcTimeout (org .threeten .bp .Duration .ofMillis (1 ))
393+ .setMaxRpcTimeout (org .threeten .bp .Duration .ofMillis (1 ))
394+ .setTotalTimeout (org .threeten .bp .Duration .ofMillis (1 ))
395+ .setMaxAttempts (1 )
396+ .build ();
397+
398+ try (EchoClient client =
399+ TestClientInitializer .createGrpcEchoClientOpentelemetryWithRetrySettings (
400+ tracerFactory , zeroRetrySettings )) {
401+
402+ assertThrows (
403+ Exception .class ,
404+ () -> client .echo (EchoRequest .newBuilder ().setContent ("metrics-test" ).build ()));
405+
406+ Thread .sleep (100 );
407+ Collection <MetricData > metrics = metricReader .collectAllMetrics ();
408+ assertThat (metrics ).isNotEmpty ();
409+
410+ MetricData durationMetric =
411+ metrics .stream ()
412+ .filter (m -> m .getName ().equals ("gcp.client.request.duration" ))
413+ .findFirst ()
414+ .orElseThrow (() -> new AssertionError ("Duration metric not found" ));
415+
416+ io .opentelemetry .api .common .Attributes attributes =
417+ durationMetric .getHistogramData ().getPoints ().iterator ().next ().getAttributes ();
418+
419+ assertThat (
420+ attributes .get (
421+ AttributeKey .stringKey (ObservabilityAttributes .RPC_RESPONSE_STATUS_ATTRIBUTE )))
422+ .isEqualTo ("DEADLINE_EXCEEDED" );
423+ assertThat (
424+ attributes .get (AttributeKey .stringKey (ObservabilityAttributes .ERROR_TYPE_ATTRIBUTE )))
425+ .isEqualTo ("DEADLINE_EXCEEDED" );
426+ }
427+ }
428+
429+ @ Test
430+ void testMetrics_zeroDeadline_httpjson () throws Exception {
431+ GoldenSignalsMetricsTracerFactory tracerFactory =
432+ new GoldenSignalsMetricsTracerFactory (openTelemetrySdk );
433+
434+ RetrySettings zeroRetrySettings =
435+ RetrySettings .newBuilder ()
436+ .setInitialRpcTimeout (org .threeten .bp .Duration .ofMillis (1 ))
437+ .setMaxRpcTimeout (org .threeten .bp .Duration .ofMillis (1 ))
438+ .setTotalTimeout (org .threeten .bp .Duration .ofMillis (1 ))
439+ .setMaxAttempts (1 )
440+ .build ();
441+
442+ try (EchoClient client =
443+ TestClientInitializer .createHttpJsonEchoClientOpentelemetryWithRetrySettings (
444+ tracerFactory , zeroRetrySettings )) {
445+
446+ assertThrows (
447+ Exception .class ,
448+ () -> client .echo (EchoRequest .newBuilder ().setContent ("metrics-test" ).build ()));
449+
450+ Thread .sleep (100 );
451+ Collection <MetricData > metrics = metricReader .collectAllMetrics ();
452+ assertThat (metrics ).isNotEmpty ();
453+
454+ MetricData durationMetric =
455+ metrics .stream ()
456+ .filter (m -> m .getName ().equals ("gcp.client.request.duration" ))
457+ .findFirst ()
458+ .orElseThrow (() -> new AssertionError ("Duration metric not found" ));
459+
460+ io .opentelemetry .api .common .Attributes attributes =
461+ durationMetric .getHistogramData ().getPoints ().iterator ().next ().getAttributes ();
462+
463+ assertThat (
464+ attributes .get (
465+ AttributeKey .stringKey (ObservabilityAttributes .RPC_RESPONSE_STATUS_ATTRIBUTE )))
466+ .isEqualTo ("DEADLINE_EXCEEDED" );
467+ assertThat (
468+ attributes .get (AttributeKey .stringKey (ObservabilityAttributes .ERROR_TYPE_ATTRIBUTE )))
469+ .isEqualTo ("504" );
470+ }
471+ }
472+
473+ @ Test
474+ void testMetrics_retryAndSucceed_grpc () throws Exception {
475+ GoldenSignalsMetricsTracerFactory tracerFactory =
476+ new GoldenSignalsMetricsTracerFactory (openTelemetrySdk );
477+
478+ RetrySettings retrySettings =
479+ RetrySettings .newBuilder ()
480+ .setInitialRpcTimeout (org .threeten .bp .Duration .ofMillis (5000L ))
481+ .setMaxRpcTimeout (org .threeten .bp .Duration .ofMillis (5000L ))
482+ .setTotalTimeout (org .threeten .bp .Duration .ofMillis (5000L ))
483+ .setMaxAttempts (3 )
484+ .build ();
485+
486+ java .util .concurrent .atomic .AtomicInteger attemptCount = new java .util .concurrent .atomic .AtomicInteger (0 );
487+
488+ ClientInterceptor interceptor =
489+ new ClientInterceptor () {
490+ @ Override
491+ public <ReqT , RespT > ClientCall <ReqT , RespT > interceptCall (
492+ MethodDescriptor <ReqT , RespT > method , CallOptions callOptions , Channel next ) {
493+ int attempt = attemptCount .incrementAndGet ();
494+ if (attempt <= 2 ) {
495+ return new ClientCall <ReqT , RespT >() {
496+ @ Override
497+ public void start (Listener <RespT > responseListener , Metadata headers ) {
498+ responseListener .onClose (io .grpc .Status .UNAVAILABLE , new Metadata ());
499+ }
500+
501+ @ Override
502+ public void request (int numMessages ) {}
503+
504+ @ Override
505+ public void cancel (String message , Throwable cause ) {}
506+
507+ @ Override
508+ public void halfClose () {}
509+
510+ @ Override
511+ public void sendMessage (ReqT message ) {}
512+ };
513+ } else {
514+ return next .interceptCall (method , callOptions );
515+ }
516+ }
517+ };
518+
519+ java .util .Set <StatusCode .Code > retryableCodes = java .util .Collections .singleton (StatusCode .Code .UNAVAILABLE );
520+
521+ try (EchoClient client =
522+ TestClientInitializer .createGrpcEchoClientOpentelemetry (
523+ tracerFactory , retrySettings , retryableCodes , ImmutableList .of (interceptor ))) {
524+
525+ client .echo (EchoRequest .newBuilder ().setContent ("metrics-test" ).build ());
526+
527+ assertThat (attemptCount .get ()).isEqualTo (3 );
528+
529+ Thread .sleep (100 );
530+ Collection <MetricData > metrics = metricReader .collectAllMetrics ();
531+ assertThat (metrics ).hasSize (1 );
532+
533+ MetricData durationMetric =
534+ metrics .stream ()
535+ .filter (m -> m .getName ().equals ("gcp.client.request.duration" ))
536+ .findFirst ()
537+ .orElseThrow (() -> new AssertionError ("Duration metric not found" ));
538+
539+ assertThat (durationMetric .getHistogramData ().getPoints ()).hasSize (1 );
540+
541+ io .opentelemetry .api .common .Attributes attributes =
542+ durationMetric .getHistogramData ().getPoints ().iterator ().next ().getAttributes ();
543+
544+ assertThat (
545+ attributes .get (
546+ AttributeKey .stringKey (ObservabilityAttributes .RPC_RESPONSE_STATUS_ATTRIBUTE )))
547+ .isEqualTo ("OK" );
548+ }
549+ }
550+
551+ @ Test
552+ void testMetrics_retryAndSucceed_httpjson () throws Exception {
553+ GoldenSignalsMetricsTracerFactory tracerFactory =
554+ new GoldenSignalsMetricsTracerFactory (openTelemetrySdk );
555+
556+ RetrySettings retrySettings =
557+ RetrySettings .newBuilder ()
558+ .setInitialRpcTimeout (org .threeten .bp .Duration .ofMillis (5000L ))
559+ .setMaxRpcTimeout (org .threeten .bp .Duration .ofMillis (5000L ))
560+ .setTotalTimeout (org .threeten .bp .Duration .ofMillis (5000L ))
561+ .setMaxAttempts (3 )
562+ .build ();
563+
564+ java .util .concurrent .atomic .AtomicInteger requestCount = new java .util .concurrent .atomic .AtomicInteger (0 );
565+
566+ HttpTransport mockTransport =
567+ new HttpTransport () {
568+ @ Override
569+ protected com .google .api .client .http .LowLevelHttpRequest buildRequest (
570+ String method , String url ) {
571+ int currentCount = requestCount .incrementAndGet ();
572+ return new com .google .api .client .http .LowLevelHttpRequest () {
573+ @ Override
574+ public void addHeader (String name , String value ) {}
575+
576+ @ Override
577+ public com .google .api .client .http .LowLevelHttpResponse execute () {
578+ if (currentCount <= 2 ) {
579+ return new com .google .api .client .http .LowLevelHttpResponse () {
580+ @ Override
581+ public InputStream getContent () {
582+ return new ByteArrayInputStream ("{}" .getBytes ());
583+ }
584+
585+ @ Override
586+ public String getContentEncoding () { return null ; }
587+
588+ @ Override
589+ public long getContentLength () { return 2 ; }
590+
591+ @ Override
592+ public String getContentType () { return "application/json" ; }
593+
594+ @ Override
595+ public String getStatusLine () { return "HTTP/1.1 503 Service Unavailable" ; }
596+
597+ @ Override
598+ public int getStatusCode () { return 503 ; }
599+
600+ @ Override
601+ public String getReasonPhrase () { return "Service Unavailable" ; }
602+
603+ @ Override
604+ public int getHeaderCount () { return 0 ; }
605+
606+ @ Override
607+ public String getHeaderName (int index ) { return null ; }
608+
609+ @ Override
610+ public String getHeaderValue (int index ) { return null ; }
611+ };
612+ } else {
613+ return new com .google .api .client .http .LowLevelHttpResponse () {
614+ @ Override
615+ public InputStream getContent () {
616+ return new ByteArrayInputStream ("{\" content\" :\" metrics-test\" }" .getBytes ());
617+ }
618+
619+ @ Override
620+ public String getContentEncoding () { return null ; }
621+
622+ @ Override
623+ public long getContentLength () { return 24 ; }
624+
625+ @ Override
626+ public String getContentType () { return "application/json" ; }
627+
628+ @ Override
629+ public String getStatusLine () { return "HTTP/1.1 200 OK" ; }
630+
631+ @ Override
632+ public int getStatusCode () { return 200 ; }
633+
634+ @ Override
635+ public String getReasonPhrase () { return "OK" ; }
636+
637+ @ Override
638+ public int getHeaderCount () { return 0 ; }
639+
640+ @ Override
641+ public String getHeaderName (int index ) { return null ; }
642+
643+ @ Override
644+ public String getHeaderValue (int index ) { return null ; }
645+ };
646+ }
647+ }
648+ };
649+ }
650+ };
651+
652+ java .util .Set <StatusCode .Code > retryableCodes = java .util .Collections .singleton (StatusCode .Code .UNAVAILABLE );
653+
654+ try (EchoClient client =
655+ TestClientInitializer .createHttpJsonEchoClientOpentelemetry (
656+ tracerFactory , retrySettings , retryableCodes , mockTransport )) {
657+
658+ client .echo (EchoRequest .newBuilder ().setContent ("metrics-test" ).build ());
659+
660+ assertThat (requestCount .get ()).isEqualTo (3 );
661+
662+ Thread .sleep (100 );
663+ Collection <MetricData > metrics = metricReader .collectAllMetrics ();
664+ assertThat (metrics ).hasSize (1 );
665+
666+ MetricData durationMetric =
667+ metrics .stream ()
668+ .filter (m -> m .getName ().equals ("gcp.client.request.duration" ))
669+ .findFirst ()
670+ .orElseThrow (() -> new AssertionError ("Duration metric not found" ));
671+
672+ assertThat (durationMetric .getHistogramData ().getPoints ()).hasSize (1 );
673+
674+ io .opentelemetry .api .common .Attributes attributes =
675+ durationMetric .getHistogramData ().getPoints ().iterator ().next ().getAttributes ();
676+
677+ assertThat (
678+ attributes .get (
679+ AttributeKey .stringKey (ObservabilityAttributes .RPC_RESPONSE_STATUS_ATTRIBUTE )))
680+ .isEqualTo ("OK" );
681+ }
682+ }
382683}
0 commit comments