2323import static com .google .cloud .bigtable .data .v2 .stub .metrics .BuiltinMetricsConstants .STATUS_KEY ;
2424import static com .google .cloud .bigtable .data .v2 .stub .metrics .BuiltinMetricsConstants .STREAMING_KEY ;
2525import static com .google .cloud .bigtable .data .v2 .stub .metrics .BuiltinMetricsConstants .TABLE_ID_KEY ;
26+ import static com .google .cloud .bigtable .data .v2 .stub .metrics .BuiltinMetricsConstants .TRANSPORT_REGION ;
27+ import static com .google .cloud .bigtable .data .v2 .stub .metrics .BuiltinMetricsConstants .TRANSPORT_SUBZONE ;
28+ import static com .google .cloud .bigtable .data .v2 .stub .metrics .BuiltinMetricsConstants .TRANSPORT_TYPE ;
29+ import static com .google .cloud .bigtable .data .v2 .stub .metrics .BuiltinMetricsConstants .TRANSPORT_ZONE ;
2630import static com .google .cloud .bigtable .data .v2 .stub .metrics .BuiltinMetricsConstants .ZONE_ID_KEY ;
2731
2832import com .google .api .core .ObsoleteApi ;
2933import com .google .api .gax .retrying .ServerStreamingAttemptException ;
3034import com .google .api .gax .tracing .SpanName ;
35+ import com .google .auto .value .AutoValue ;
3136import com .google .cloud .bigtable .Version ;
3237import com .google .common .base .Stopwatch ;
38+ import com .google .common .base .Strings ;
3339import com .google .common .math .IntMath ;
40+ import com .google .gson .Gson ;
41+ import com .google .gson .reflect .TypeToken ;
3442import io .grpc .Deadline ;
3543import io .opentelemetry .api .common .Attributes ;
3644import io .opentelemetry .api .metrics .DoubleHistogram ;
3745import io .opentelemetry .api .metrics .LongCounter ;
3846import java .time .Duration ;
47+ import java .util .Map ;
3948import java .util .concurrent .CancellationException ;
4049import java .util .concurrent .TimeUnit ;
4150import java .util .concurrent .atomic .AtomicBoolean ;
4251import java .util .concurrent .atomic .AtomicInteger ;
4352import java .util .concurrent .atomic .AtomicLong ;
53+ import java .util .logging .Level ;
4454import java .util .logging .Logger ;
4555import javax .annotation .Nullable ;
4656
4959 * bigtable.googleapis.com/client namespace
5060 */
5161class BuiltinMetricsTracer extends BigtableTracer {
62+ @ AutoValue
63+ abstract static class TransportAttrs {
64+ @ Nullable
65+ abstract String getLocality ();
66+
67+ @ Nullable
68+ abstract String getBackendService ();
69+
70+ static TransportAttrs create (@ Nullable String locality , @ Nullable String backendService ) {
71+ return new AutoValue_BuiltinMetricsTracer_TransportAttrs (locality , backendService );
72+ }
73+ }
5274
5375 private static final Logger logger = Logger .getLogger (BuiltinMetricsTracer .class .getName ());
76+ private static final Gson GSON = new Gson ();
77+ private static final TypeToken <Map <String , String >> LOCALITY_TYPE =
78+ new TypeToken <Map <String , String >>() {};
5479
5580 private static final String NAME = "java-bigtable/" + Version .VERSION ;
5681 private final OperationType operationType ;
@@ -95,12 +120,15 @@ class BuiltinMetricsTracer extends BigtableTracer {
95120 private Deadline operationDeadline = null ;
96121 private volatile long remainingDeadlineAtAttemptStart = 0 ;
97122
123+ private TransportAttrs transportAttrs = null ;
124+
98125 // OpenCensus (and server) histogram buckets use [start, end), however OpenTelemetry uses (start,
99126 // end]. To work around this, we measure all the latencies in nanoseconds and convert them
100127 // to milliseconds and use DoubleHistogram. This should minimize the chance of a data
101128 // point fall on the bucket boundary that causes off by one errors.
102129 private final DoubleHistogram operationLatenciesHistogram ;
103130 private final DoubleHistogram attemptLatenciesHistogram ;
131+ private final DoubleHistogram attemptLatencies2Histogram ;
104132 private final DoubleHistogram serverLatenciesHistogram ;
105133 private final DoubleHistogram firstResponseLatenciesHistogram ;
106134 private final DoubleHistogram clientBlockingLatenciesHistogram ;
@@ -115,6 +143,7 @@ class BuiltinMetricsTracer extends BigtableTracer {
115143 Attributes attributes ,
116144 DoubleHistogram operationLatenciesHistogram ,
117145 DoubleHistogram attemptLatenciesHistogram ,
146+ DoubleHistogram attemptLatencies2Histogram ,
118147 DoubleHistogram serverLatenciesHistogram ,
119148 DoubleHistogram firstResponseLatenciesHistogram ,
120149 DoubleHistogram clientBlockingLatenciesHistogram ,
@@ -128,6 +157,7 @@ class BuiltinMetricsTracer extends BigtableTracer {
128157
129158 this .operationLatenciesHistogram = operationLatenciesHistogram ;
130159 this .attemptLatenciesHistogram = attemptLatenciesHistogram ;
160+ this .attemptLatencies2Histogram = attemptLatencies2Histogram ;
131161 this .serverLatenciesHistogram = serverLatenciesHistogram ;
132162 this .firstResponseLatenciesHistogram = firstResponseLatenciesHistogram ;
133163 this .clientBlockingLatenciesHistogram = clientBlockingLatenciesHistogram ;
@@ -301,6 +331,11 @@ public void setLocations(String zone, String cluster) {
301331 this .cluster = cluster ;
302332 }
303333
334+ @ Override
335+ public void setTransportAttrs (TransportAttrs attrs ) {
336+ this .transportAttrs = attrs ;
337+ }
338+
304339 @ Override
305340 public void batchRequestThrottled (long throttledTimeNanos ) {
306341 totalClientBlockingTime .addAndGet (java .time .Duration .ofNanos (throttledTimeNanos ).toMillis ());
@@ -417,6 +452,35 @@ private void recordAttemptCompletion(@Nullable Throwable status) {
417452 attemptLatenciesHistogram .record (
418453 convertToMs (attemptTimer .elapsed (TimeUnit .NANOSECONDS )), attributes );
419454
455+ String transportType = "cloudpath" ;
456+ String transportRegion = "" ;
457+ String transportZone = "" ;
458+ String transportSubzone = "" ;
459+
460+ try {
461+ if (transportAttrs != null && !Strings .isNullOrEmpty (transportAttrs .getLocality ())) {
462+ // only directpath has locality
463+ transportType = "directpath" ;
464+ Map <String , String > localityMap =
465+ GSON .fromJson (transportAttrs .getLocality (), LOCALITY_TYPE );
466+ transportRegion = localityMap .getOrDefault ("region" , "" );
467+ transportZone = localityMap .getOrDefault ("zone" , "" );
468+ transportSubzone = localityMap .getOrDefault ("sub_zone" , "" );
469+ }
470+ } catch (RuntimeException e ) {
471+ logger .log (
472+ Level .WARNING , "Failed to parse transport locality: " + transportAttrs .getLocality (), e );
473+ }
474+ attemptLatencies2Histogram .record (
475+ convertToMs (attemptTimer .elapsed (TimeUnit .NANOSECONDS )),
476+ attributes
477+ .toBuilder ()
478+ .put (TRANSPORT_TYPE , transportType )
479+ .put (TRANSPORT_REGION , transportRegion )
480+ .put (TRANSPORT_ZONE , transportZone )
481+ .put (TRANSPORT_SUBZONE , transportSubzone )
482+ .build ());
483+
420484 // When operationDeadline is set, it's possible that the deadline is passed by the time we send
421485 // a new attempt. In this case we'll record 0.
422486 if (operationDeadline != null ) {
0 commit comments