diff --git a/CHANGELOG.md b/CHANGELOG.md index a3eac07be1..82ee75f8e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [3.29.0](https://github.com/googleapis/java-firestore/compare/v3.28.0...v3.29.0) (2024-11-13) + + +### Bug Fixes + +* Drop the setTracingEnabled flag from Options (@BetaApi change) ([#1869](https://github.com/googleapis/java-firestore/issues/1869)) ([712d57e](https://github.com/googleapis/java-firestore/commit/712d57e05da8de41850d94fa19eb253cf3020c0f)) +* Improve error messages in tests ([#1855](https://github.com/googleapis/java-firestore/issues/1855)) ([cb5f9d6](https://github.com/googleapis/java-firestore/commit/cb5f9d6f481c9c839a387579bf60742b7c2c0508)) +* Make StreamableQuery#internalStream package-private to avoid documentation and prevent clirr errors ([593acc3](https://github.com/googleapis/java-firestore/commit/593acc3614f86bdf42387eac5ef8d7ce14dbbec8)) + + +### Dependencies + +* Update opentelemetry.version to v1.44.1 ([#1924](https://github.com/googleapis/java-firestore/issues/1924)) ([68deeac](https://github.com/googleapis/java-firestore/commit/68deeac02eabb927ba8b91c77f3be8471ba66718)) + ## [3.28.0](https://github.com/googleapis/java-firestore/compare/v3.27.4...v3.28.0) (2024-10-29) diff --git a/generation_config.yaml b/generation_config.yaml index b91c21240f..070aa2045d 100644 --- a/generation_config.yaml +++ b/generation_config.yaml @@ -1,6 +1,6 @@ gapic_generator_version: 2.49.0 -googleapis_commitish: ba8ea80f25d19bde8501cd51f314391f8d39bde8 -libraries_bom_version: 26.49.0 +googleapis_commitish: 1f2e5aab4f95b9bd38dd1ac8c7486657f93c1975 +libraries_bom_version: 26.50.0 libraries: - api_shortname: firestore name_pretty: Cloud Firestore diff --git a/google-cloud-firestore-admin/pom.xml b/google-cloud-firestore-admin/pom.xml index 695693f298..fbb61cfec1 100644 --- a/google-cloud-firestore-admin/pom.xml +++ b/google-cloud-firestore-admin/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 google-cloud-firestore-admin - 3.28.0 + 3.29.0 jar Google Cloud Firestore Admin Client https://github.com/googleapis/java-firestore @@ -14,7 +14,7 @@ com.google.cloud google-cloud-firestore-parent - 3.28.0 + 3.29.0 diff --git a/google-cloud-firestore-bom/pom.xml b/google-cloud-firestore-bom/pom.xml index 67eade58ba..8aa24a9edf 100644 --- a/google-cloud-firestore-bom/pom.xml +++ b/google-cloud-firestore-bom/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-firestore-bom - 3.28.0 + 3.29.0 pom com.google.cloud @@ -52,37 +52,37 @@ com.google.cloud google-cloud-firestore - 3.28.0 + 3.29.0 com.google.cloud google-cloud-firestore-admin - 3.28.0 + 3.29.0 com.google.api.grpc grpc-google-cloud-firestore-admin-v1 - 3.28.0 + 3.29.0 com.google.api.grpc grpc-google-cloud-firestore-v1 - 3.28.0 + 3.29.0 com.google.api.grpc proto-google-cloud-firestore-admin-v1 - 3.28.0 + 3.29.0 com.google.api.grpc proto-google-cloud-firestore-v1 - 3.28.0 + 3.29.0 com.google.cloud proto-google-cloud-firestore-bundle-v1 - 3.28.0 + 3.29.0 diff --git a/google-cloud-firestore/clirr-ignored-differences.xml b/google-cloud-firestore/clirr-ignored-differences.xml index fd6e9e5b0e..a1064b15cf 100644 --- a/google-cloud-firestore/clirr-ignored-differences.xml +++ b/google-cloud-firestore/clirr-ignored-differences.xml @@ -314,4 +314,36 @@ com.google.cloud.firestore.Query getQuery() + + + 7002 + com/google/cloud/firestore/FirestoreOpenTelemetryOptions + boolean isTracingEnabled() + + + 7002 + com/google/cloud/firestore/FirestoreOpenTelemetryOptions$Builder + com.google.cloud.firestore.FirestoreOpenTelemetryOptions$Builder setTracingEnabled(boolean) + + + + com/google/cloud/firestore/StreamableQuery + 7009 + void internalStream(*) + + + + + 6011 + com/google/cloud/firestore/telemetry/TraceUtil + SPAN_NAME_* + + + + + 7005 + com/google/cloud/firestore/StreamableQuery + void internalStream(*) + * + diff --git a/google-cloud-firestore/pom.xml b/google-cloud-firestore/pom.xml index c1399288fe..9e3894a63f 100644 --- a/google-cloud-firestore/pom.xml +++ b/google-cloud-firestore/pom.xml @@ -2,7 +2,7 @@ 4.0.0 google-cloud-firestore - 3.28.0 + 3.29.0 jar Google Cloud Firestore https://github.com/googleapis/java-firestore @@ -12,11 +12,11 @@ com.google.cloud google-cloud-firestore-parent - 3.28.0 + 3.29.0 google-cloud-firestore - 1.43.0 + 1.44.1 @@ -119,6 +119,18 @@ io.opentelemetry.instrumentation opentelemetry-grpc-1.6 + + io.opentelemetry + opentelemetry-sdk + + + io.opentelemetry + opentelemetry-sdk-metrics + + + com.google.cloud.opentelemetry + exporter-metrics + @@ -181,12 +193,6 @@ test - - io.opentelemetry - opentelemetry-sdk - ${opentelemetry.version} - test - io.opentelemetry opentelemetry-sdk-testing @@ -225,20 +231,6 @@ 2.53.0 test - - com.google.cloud.opentelemetry - exporter-trace - 0.33.0 - test - - - - - com.google.api.grpc - proto-google-cloud-trace-v1 - 2.53.0 - test - com.google.cloud google-cloud-trace diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQuery.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQuery.java index 84b4a04781..89702e4230 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQuery.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQuery.java @@ -16,8 +16,8 @@ package com.google.cloud.firestore; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_RUN_AGGREGATION_QUERY; import static com.google.cloud.firestore.telemetry.TraceUtil.ATTRIBUTE_KEY_ATTEMPT; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_RUN_AGGREGATION_QUERY; import com.google.api.core.ApiFuture; import com.google.api.core.InternalExtensionOnly; @@ -27,6 +27,9 @@ import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.StreamController; import com.google.cloud.Timestamp; +import com.google.cloud.firestore.telemetry.MetricsUtil.MetricsContext; +import com.google.cloud.firestore.telemetry.TelemetryConstants; +import com.google.cloud.firestore.telemetry.TelemetryConstants.MetricType; import com.google.cloud.firestore.telemetry.TraceUtil; import com.google.cloud.firestore.telemetry.TraceUtil.Scope; import com.google.cloud.firestore.v1.FirestoreSettings; @@ -70,6 +73,11 @@ private TraceUtil getTraceUtil() { return query.getFirestore().getOptions().getTraceUtil(); } + @Nonnull + private MetricsContext createMetricsContext(String method) { + return query.getFirestore().getOptions().getMetricsUtil().createMetricsContext(method); + } + /** Returns the query whose aggregations will be calculated by this object. */ @Nonnull public Query getQuery() { @@ -96,14 +104,20 @@ public ApiFuture get() { */ @Nonnull public ApiFuture> explain(ExplainOptions options) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_AGGREGATION_QUERY_GET); + TraceUtil.Span span = + getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_AGGREGATION_QUERY_GET); + + MetricsContext metricsContext = + createMetricsContext(TelemetryConstants.METHOD_NAME_RUN_AGGREGATION_QUERY_EXPLAIN); + try (Scope ignored = span.makeCurrent()) { AggregateQueryExplainResponseDeliverer responseDeliverer = new AggregateQueryExplainResponseDeliverer( /* transactionId= */ null, /* readTime= */ null, /* startTimeNanos= */ query.rpcContext.getClock().nanoTime(), - /* explainOptions= */ options); + /* explainOptions= */ options, + metricsContext); runQuery(responseDeliverer, /* attempt */ 0); ApiFuture> result = responseDeliverer.getFuture(); span.endAtFuture(result); @@ -121,14 +135,22 @@ ApiFuture get( getTraceUtil() .startSpan( transactionId == null - ? TraceUtil.SPAN_NAME_AGGREGATION_QUERY_GET - : TraceUtil.SPAN_NAME_TRANSACTION_GET_AGGREGATION_QUERY); + ? TelemetryConstants.METHOD_NAME_AGGREGATION_QUERY_GET + : TelemetryConstants.METHOD_NAME_TRANSACTION_GET_AGGREGATION_QUERY); + + MetricsContext metricsContext = + createMetricsContext( + transactionId == null + ? TelemetryConstants.METHOD_NAME_RUN_AGGREGATION_QUERY_GET + : TelemetryConstants.METHOD_NAME_RUN_AGGREGATION_QUERY_TRANSACTIONAL); + try (Scope ignored = span.makeCurrent()) { AggregateQueryResponseDeliverer responseDeliverer = new AggregateQueryResponseDeliverer( transactionId, readTime, - /* startTimeNanos= */ query.rpcContext.getClock().nanoTime()); + /* startTimeNanos= */ query.rpcContext.getClock().nanoTime(), + metricsContext); runQuery(responseDeliverer, /* attempt= */ 0); ApiFuture result = responseDeliverer.getFuture(); span.endAtFuture(result); @@ -165,14 +187,17 @@ private abstract static class ResponseDeliverer { private final @Nullable com.google.protobuf.Timestamp readTime; private final long startTimeNanos; private final SettableApiFuture future = SettableApiFuture.create(); + private MetricsContext metricsContext; ResponseDeliverer( @Nullable ByteString transactionId, @Nullable com.google.protobuf.Timestamp readTime, - long startTimeNanos) { + long startTimeNanos, + MetricsContext metricsContext) { this.transactionId = transactionId; this.readTime = readTime; this.startTimeNanos = startTimeNanos; + this.metricsContext = metricsContext; } @Nullable @@ -198,15 +223,29 @@ ApiFuture getFuture() { return future; } - protected void setFuture(T value) { - future.set(value); + void deliverFirstResponse() { + metricsContext.recordLatency(MetricType.FIRST_RESPONSE_LATENCY); } void deliverError(Throwable throwable) { future.setException(throwable); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, throwable); } - abstract void deliverResult( + void deliverResult( + @Nullable Map serverData, + Timestamp readTime, + @Nullable ExplainMetrics metrics) { + try { + T result = processResult(serverData, readTime, metrics); + future.set(result); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY); + } catch (Exception error) { + deliverError(error); + } + } + + abstract T processResult( @Nullable Map serverData, Timestamp readTime, @Nullable ExplainMetrics metrics); @@ -216,24 +255,23 @@ private class AggregateQueryResponseDeliverer extends ResponseDeliverer serverData, Timestamp readTime, @Nullable ExplainMetrics metrics) { if (serverData == null) { - deliverError(new RuntimeException("Did not receive any aggregate query results.")); - return; + throw new RuntimeException("Did not receive any aggregate query results."); } - setFuture( - new AggregateQuerySnapshot( - AggregateQuery.this, - readTime, - convertServerAggregateFieldsMapToClientAggregateFieldsMap(serverData))); + return new AggregateQuerySnapshot( + AggregateQuery.this, + readTime, + convertServerAggregateFieldsMapToClientAggregateFieldsMap(serverData)); } } @@ -245,8 +283,9 @@ private final class AggregateQueryExplainResponseDeliverer @Nullable ByteString transactionId, @Nullable com.google.protobuf.Timestamp readTime, long startTimeNanos, - @Nullable ExplainOptions explainOptions) { - super(transactionId, readTime, startTimeNanos); + @Nullable ExplainOptions explainOptions, + MetricsContext metricsContext) { + super(transactionId, readTime, startTimeNanos, metricsContext); this.explainOptions = explainOptions; } @@ -257,14 +296,13 @@ ExplainOptions getExplainOptions() { } @Override - void deliverResult( + ExplainResults processResult( @Nullable Map serverData, Timestamp readTime, @Nullable ExplainMetrics metrics) { // The server is required to provide ExplainMetrics for explain queries. if (metrics == null) { - deliverError(new RuntimeException("Did not receive any metrics for explain query.")); - return; + throw new RuntimeException("Did not receive any metrics for explain query."); } AggregateQuerySnapshot snapshot = serverData == null @@ -273,7 +311,7 @@ void deliverResult( AggregateQuery.this, readTime, convertServerAggregateFieldsMapToClientAggregateFieldsMap(serverData)); - setFuture(new ExplainResults<>(metrics, snapshot)); + return new ExplainResults<>(metrics, snapshot); } } @@ -284,6 +322,7 @@ private final class AggregateQueryResponseObserver @Nullable private Map aggregateFieldsMap = null; @Nullable private ExplainMetrics metrics = null; private int attempt; + private boolean firstResponse = false; AggregateQueryResponseObserver(ResponseDeliverer responseDeliverer, int attempt) { this.responseDeliverer = responseDeliverer; @@ -302,15 +341,20 @@ private boolean isExplainQuery() { public void onStart(StreamController streamController) { getTraceUtil() .currentSpan() - .addEvent(SPAN_NAME_RUN_AGGREGATION_QUERY + " Stream started.", getAttemptAttributes()); + .addEvent(METHOD_NAME_RUN_AGGREGATION_QUERY + " Stream started.", getAttemptAttributes()); } @Override public void onResponse(RunAggregationQueryResponse response) { + if (!firstResponse) { + firstResponse = true; + responseDeliverer.deliverFirstResponse(); + } + getTraceUtil() .currentSpan() .addEvent( - SPAN_NAME_RUN_AGGREGATION_QUERY + " Response Received.", getAttemptAttributes()); + METHOD_NAME_RUN_AGGREGATION_QUERY + " Response Received.", getAttemptAttributes()); if (response.hasReadTime()) { readTime = Timestamp.fromProto(response.getReadTime()); } @@ -339,16 +383,16 @@ public void onError(Throwable throwable) { getTraceUtil() .currentSpan() .addEvent( - SPAN_NAME_RUN_AGGREGATION_QUERY + ": Retryable Error", - Collections.singletonMap("error.message", throwable.getMessage())); + METHOD_NAME_RUN_AGGREGATION_QUERY + ": Retryable Error", + Collections.singletonMap("error.message", throwable.toString())); runQuery(responseDeliverer, attempt + 1); } else { getTraceUtil() .currentSpan() .addEvent( - SPAN_NAME_RUN_AGGREGATION_QUERY + ": Error", - Collections.singletonMap("error.message", throwable.getMessage())); + METHOD_NAME_RUN_AGGREGATION_QUERY + ": Error", + Collections.singletonMap("error.message", throwable.toString())); responseDeliverer.deliverError(throwable); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkWriter.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkWriter.java index bc881979c0..0a713fe401 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkWriter.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/BulkWriter.java @@ -26,6 +26,9 @@ import com.google.api.core.SettableApiFuture; import com.google.api.gax.rpc.ApiException; import com.google.api.gax.rpc.StatusCode.Code; +import com.google.cloud.firestore.telemetry.MetricsUtil.MetricsContext; +import com.google.cloud.firestore.telemetry.TelemetryConstants; +import com.google.cloud.firestore.telemetry.TelemetryConstants.MetricType; import com.google.cloud.firestore.telemetry.TraceUtil; import com.google.cloud.firestore.telemetry.TraceUtil.Context; import com.google.cloud.firestore.telemetry.TraceUtil.Scope; @@ -912,8 +915,15 @@ private void sendBatchLocked(final BulkCommitBatch batch, final boolean flush) { firestore .getOptions() .getTraceUtil() - .startSpan(TraceUtil.SPAN_NAME_BULK_WRITER_COMMIT, traceContext) + .startSpan(TelemetryConstants.METHOD_NAME_BULK_WRITER_COMMIT, traceContext) .setAttribute(ATTRIBUTE_KEY_DOC_COUNT, batch.getMutationsSize()); + + MetricsContext metricsContext = + firestore + .getOptions() + .getMetricsUtil() + .createMetricsContext(TelemetryConstants.METHOD_NAME_BULK_WRITER_COMMIT); + try (Scope ignored = span.makeCurrent()) { ApiFuture result = batch.bulkCommit(); result.addListener( @@ -926,8 +936,10 @@ private void sendBatchLocked(final BulkCommitBatch batch, final boolean flush) { }, bulkWriterExecutor); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } else { diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionGroup.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionGroup.java index d81a0489ea..159f7077d8 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionGroup.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionGroup.java @@ -21,6 +21,9 @@ import com.google.api.gax.rpc.ApiException; import com.google.api.gax.rpc.ApiExceptions; import com.google.api.gax.rpc.ApiStreamObserver; +import com.google.cloud.firestore.telemetry.MetricsUtil.MetricsContext; +import com.google.cloud.firestore.telemetry.TelemetryConstants; +import com.google.cloud.firestore.telemetry.TelemetryConstants.MetricType; import com.google.cloud.firestore.telemetry.TraceUtil; import com.google.cloud.firestore.telemetry.TraceUtil.Scope; import com.google.cloud.firestore.v1.FirestoreClient.PartitionQueryPagedResponse; @@ -109,7 +112,15 @@ public ApiFuture> getPartitions(long desiredPartitionCount) .getFirestore() .getOptions() .getTraceUtil() - .startSpan(TraceUtil.SPAN_NAME_PARTITION_QUERY); + .startSpan(TelemetryConstants.METHOD_NAME_PARTITION_QUERY); + + MetricsContext metricsContext = + rpcContext + .getFirestore() + .getOptions() + .getMetricsUtil() + .createMetricsContext(TelemetryConstants.METHOD_NAME_PARTITION_QUERY); + try (Scope ignored = span.makeCurrent()) { ApiFuture> result = ApiFutures.transform( @@ -127,12 +138,15 @@ public ApiFuture> getPartitions(long desiredPartitionCount) }, MoreExecutors.directExecutor()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (ApiException exception) { span.end(exception); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, exception); throw FirestoreException.forApiException(exception); } catch (Throwable throwable) { span.end(throwable); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, throwable); throw throwable; } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionReference.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionReference.java index e1c2841d68..75954d82de 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionReference.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionReference.java @@ -24,6 +24,9 @@ import com.google.api.gax.rpc.UnaryCallable; import com.google.cloud.firestore.encoding.CustomClassMapper; import com.google.cloud.firestore.spi.v1.FirestoreRpc; +import com.google.cloud.firestore.telemetry.MetricsUtil.MetricsContext; +import com.google.cloud.firestore.telemetry.TelemetryConstants; +import com.google.cloud.firestore.telemetry.TelemetryConstants.MetricType; import com.google.cloud.firestore.telemetry.TraceUtil; import com.google.cloud.firestore.telemetry.TraceUtil.Scope; import com.google.cloud.firestore.v1.FirestoreClient.ListDocumentsPagedResponse; @@ -134,7 +137,15 @@ public Iterable listDocuments() { .getFirestore() .getOptions() .getTraceUtil() - .startSpan(TraceUtil.SPAN_NAME_COL_REF_LIST_DOCUMENTS); + .startSpan(TelemetryConstants.METHOD_NAME_COL_REF_LIST_DOCUMENTS); + + MetricsContext metricsContext = + rpcContext + .getFirestore() + .getOptions() + .getMetricsUtil() + .createMetricsContext(TelemetryConstants.METHOD_NAME_COL_REF_LIST_DOCUMENTS); + try (Scope ignored = span.makeCurrent()) { ListDocumentsRequest.Builder request = ListDocumentsRequest.newBuilder(); request.setParent(options.getParentPath().toString()); @@ -174,12 +185,15 @@ public void remove() { } }; span.end(); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY); return result; } catch (ApiException exception) { span.end(exception); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, exception); throw FirestoreException.forApiException(exception); } catch (Throwable throwable) { span.end(throwable); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, throwable); throw throwable; } } @@ -200,7 +214,15 @@ public ApiFuture add(@Nonnull final Map field .getFirestore() .getOptions() .getTraceUtil() - .startSpan(TraceUtil.SPAN_NAME_COL_REF_ADD); + .startSpan(TelemetryConstants.METHOD_NAME_COL_REF_ADD); + + MetricsContext metricsContext = + rpcContext + .getFirestore() + .getOptions() + .getMetricsUtil() + .createMetricsContext(TelemetryConstants.METHOD_NAME_COL_REF_ADD); + try (Scope ignored = span.makeCurrent()) { final DocumentReference documentReference = document(); ApiFuture createFuture = documentReference.create(fields); @@ -208,9 +230,11 @@ public ApiFuture add(@Nonnull final Map field ApiFutures.transform( createFuture, writeResult -> documentReference, MoreExecutors.directExecutor()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/DocumentReference.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/DocumentReference.java index 57254bb2b6..2b0cc1ddc9 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/DocumentReference.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/DocumentReference.java @@ -21,6 +21,10 @@ import com.google.api.core.InternalExtensionOnly; import com.google.api.gax.rpc.ApiException; import com.google.api.gax.rpc.ApiExceptions; +import com.google.cloud.firestore.telemetry.MetricsUtil; +import com.google.cloud.firestore.telemetry.MetricsUtil.MetricsContext; +import com.google.cloud.firestore.telemetry.TelemetryConstants; +import com.google.cloud.firestore.telemetry.TelemetryConstants.MetricType; import com.google.cloud.firestore.telemetry.TraceUtil; import com.google.cloud.firestore.telemetry.TraceUtil.Scope; import com.google.cloud.firestore.v1.FirestoreClient.ListCollectionIdsPagedResponse; @@ -139,6 +143,11 @@ private TraceUtil getTraceUtil() { return getFirestore().getOptions().getTraceUtil(); } + @Nonnull + private MetricsUtil getMetricsUtil() { + return getFirestore().getOptions().getMetricsUtil(); + } + /** * Creates a new Document at the DocumentReference's Location. It fails the write if the document * exists. @@ -148,14 +157,20 @@ private TraceUtil getTraceUtil() { */ @Nonnull public ApiFuture create(@Nonnull Map fields) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_CREATE); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_CREATE); + + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_CREATE); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst(writeBatch.create(this, fields).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -169,14 +184,19 @@ public ApiFuture create(@Nonnull Map fields) { */ @Nonnull public ApiFuture create(@Nonnull Object pojo) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_CREATE); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_CREATE); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_CREATE); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst(writeBatch.create(this, pojo).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -190,14 +210,19 @@ public ApiFuture create(@Nonnull Object pojo) { */ @Nonnull public ApiFuture set(@Nonnull Map fields) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_SET); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_SET); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_SET); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst(writeBatch.set(this, fields).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -214,14 +239,19 @@ public ApiFuture set(@Nonnull Map fields) { @Nonnull public ApiFuture set( @Nonnull Map fields, @Nonnull SetOptions options) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_SET); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_SET); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_SET); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst(writeBatch.set(this, fields, options).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -235,14 +265,19 @@ public ApiFuture set( */ @Nonnull public ApiFuture set(@Nonnull Object pojo) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_SET); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_SET); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_SET); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst(writeBatch.set(this, pojo).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -258,14 +293,19 @@ public ApiFuture set(@Nonnull Object pojo) { */ @Nonnull public ApiFuture set(@Nonnull Object pojo, @Nonnull SetOptions options) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_SET); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_SET); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_SET); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst(writeBatch.set(this, pojo, options).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -279,14 +319,19 @@ public ApiFuture set(@Nonnull Object pojo, @Nonnull SetOptions opti */ @Nonnull public ApiFuture update(@Nonnull Map fields) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_UPDATE); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_UPDATE); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_UPDATE); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst(writeBatch.update(this, fields).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -301,15 +346,20 @@ public ApiFuture update(@Nonnull Map fields) { */ @Nonnull public ApiFuture update(@Nonnull Map fields, Precondition options) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_UPDATE); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_UPDATE); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_UPDATE); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst(writeBatch.update(this, fields, options).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -326,15 +376,20 @@ public ApiFuture update(@Nonnull Map fields, Precon @Nonnull public ApiFuture update( @Nonnull String field, @Nullable Object value, Object... moreFieldsAndValues) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_UPDATE); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_UPDATE); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_UPDATE); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst(writeBatch.update(this, field, value, moreFieldsAndValues).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -351,15 +406,20 @@ public ApiFuture update( @Nonnull public ApiFuture update( @Nonnull FieldPath fieldPath, @Nullable Object value, Object... moreFieldsAndValues) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_UPDATE); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_UPDATE); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_UPDATE); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst(writeBatch.update(this, fieldPath, value, moreFieldsAndValues).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -380,16 +440,21 @@ public ApiFuture update( @Nonnull String field, @Nullable Object value, Object... moreFieldsAndValues) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_UPDATE); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_UPDATE); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_UPDATE); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst( writeBatch.update(this, options, field, value, moreFieldsAndValues).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -410,16 +475,21 @@ public ApiFuture update( @Nonnull FieldPath fieldPath, @Nullable Object value, Object... moreFieldsAndValues) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_UPDATE); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_UPDATE); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_UPDATE); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst( writeBatch.update(this, options, fieldPath, value, moreFieldsAndValues).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -432,14 +502,19 @@ public ApiFuture update( */ @Nonnull public ApiFuture delete(@Nonnull Precondition options) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_DELETE); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_DELETE); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_DELETE); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst(writeBatch.delete(this, options).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -451,14 +526,19 @@ public ApiFuture delete(@Nonnull Precondition options) { */ @Nonnull public ApiFuture delete() { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_DELETE); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_DELETE); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_DELETE); + try (Scope ignored = span.makeCurrent()) { WriteBatch writeBatch = rpcContext.getFirestore().batch(); ApiFuture result = extractFirst(writeBatch.delete(this).commit()); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -472,13 +552,18 @@ public ApiFuture delete() { */ @Nonnull public ApiFuture get() { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_GET); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_GET); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_GET); + try (Scope ignored = span.makeCurrent()) { ApiFuture result = extractFirst(rpcContext.getFirestore().getAll(this)); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -493,14 +578,19 @@ public ApiFuture get() { */ @Nonnull public ApiFuture get(FieldMask fieldMask) { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_GET); + TraceUtil.Span span = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_GET); + MetricsContext metricsContext = + getMetricsUtil().createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_GET); + try (Scope ignored = span.makeCurrent()) { ApiFuture result = extractFirst(rpcContext.getFirestore().getAll(new DocumentReference[] {this}, fieldMask)); span.endAtFuture(result); + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -513,7 +603,12 @@ public ApiFuture get(FieldMask fieldMask) { */ @Nonnull public Iterable listCollections() { - TraceUtil.Span span = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_DOC_REF_LIST_COLLECTIONS); + TraceUtil.Span span = + getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_DOC_REF_LIST_COLLECTIONS); + MetricsContext metricsContext = + getMetricsUtil() + .createMetricsContext(TelemetryConstants.METHOD_NAME_DOC_REF_LIST_COLLECTIONS); + try (Scope ignored = span.makeCurrent()) { ListCollectionIdsRequest.Builder request = ListCollectionIdsRequest.newBuilder(); request.setParent(path.toString()); @@ -547,9 +642,11 @@ public void remove() { } }; span.end(); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY); return result; } catch (ApiException exception) { span.end(exception); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, exception); throw FirestoreException.forApiException(exception); } } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FirestoreImpl.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FirestoreImpl.java index 48c691466e..5c72171412 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FirestoreImpl.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FirestoreImpl.java @@ -32,6 +32,9 @@ import com.google.api.gax.rpc.UnaryCallable; import com.google.cloud.Timestamp; import com.google.cloud.firestore.spi.v1.FirestoreRpc; +import com.google.cloud.firestore.telemetry.MetricsUtil.MetricsContext; +import com.google.cloud.firestore.telemetry.TelemetryConstants; +import com.google.cloud.firestore.telemetry.TelemetryConstants.MetricType; import com.google.cloud.firestore.telemetry.TraceUtil; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; @@ -227,6 +230,14 @@ void getAll( // that we receive from the server. final int NUM_RESPONSES_PER_TRACE_EVENT = 100; + MetricsContext metricsContext = + getOptions() + .getMetricsUtil() + .createMetricsContext( + transactionId == null + ? TelemetryConstants.METHOD_NAME_BATCH_GET_DOCUMENTS_GET_ALL + : TelemetryConstants.METHOD_NAME_BATCH_GET_DOCUMENTS_TRANSACTIONAL); + ResponseObserver responseObserver = new ResponseObserver() { int numResponses = 0; @@ -237,7 +248,7 @@ public void onStart(StreamController streamController) { getTraceUtil() .currentSpan() .addEvent( - TraceUtil.SPAN_NAME_BATCH_GET_DOCUMENTS + ": Start", + TelemetryConstants.METHOD_NAME_BATCH_GET_DOCUMENTS + ": Start", new ImmutableMap.Builder() .put(ATTRIBUTE_KEY_DOC_COUNT, documentReferences.length) .put(ATTRIBUTE_KEY_IS_TRANSACTIONAL, transactionId != null) @@ -253,12 +264,15 @@ public void onResponse(BatchGetDocumentsResponse response) { if (numResponses == 1) { getTraceUtil() .currentSpan() - .addEvent(TraceUtil.SPAN_NAME_BATCH_GET_DOCUMENTS + ": First response received"); + .addEvent( + TelemetryConstants.METHOD_NAME_BATCH_GET_DOCUMENTS + + ": First response received"); + metricsContext.recordLatency(MetricType.FIRST_RESPONSE_LATENCY); } else if (numResponses % NUM_RESPONSES_PER_TRACE_EVENT == 0) { getTraceUtil() .currentSpan() .addEvent( - TraceUtil.SPAN_NAME_BATCH_GET_DOCUMENTS + TelemetryConstants.METHOD_NAME_BATCH_GET_DOCUMENTS + ": Received " + numResponses + " responses"); @@ -298,6 +312,7 @@ public void onResponse(BatchGetDocumentsResponse response) { @Override public void onError(Throwable throwable) { getTraceUtil().currentSpan().end(throwable); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, throwable); apiStreamObserver.onError(throwable); } @@ -308,11 +323,12 @@ public void onComplete() { getTraceUtil() .currentSpan() .addEvent( - TraceUtil.SPAN_NAME_BATCH_GET_DOCUMENTS + TelemetryConstants.METHOD_NAME_BATCH_GET_DOCUMENTS + ": Completed with " + numResponses + " responses.", Collections.singletonMap(ATTRIBUTE_KEY_NUM_RESPONSES, numResponses)); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY); apiStreamObserver.onCompleted(); } }; @@ -427,16 +443,30 @@ public ApiFuture runAsyncTransaction( @Nonnull final Transaction.AsyncFunction updateFunction, @Nonnull TransactionOptions transactionOptions) { - if (transactionOptions.getReadTime() != null) { - // READ_ONLY transactions with readTime have no retry, nor transaction state, so we don't need - // a runner. - return updateFunction.updateCallback( - new ReadTimeTransaction(this, transactionOptions.getReadTime())); - } else { - // For READ_ONLY transactions without readTime, there is still strong consistency applied, - // that cannot be tracked client side. - return new ServerSideTransactionRunner<>(this, updateFunction, transactionOptions).run(); + MetricsContext metricsContext = + getOptions() + .getMetricsUtil() + .createMetricsContext(TelemetryConstants.METHOD_NAME_RUN_TRANSACTION); + ApiFuture result; + + try { + if (transactionOptions.getReadTime() != null) { + // READ_ONLY transactions with readTime have no retry, nor transaction state, so we don't + // need a runner. + result = + updateFunction.updateCallback( + new ReadTimeTransaction(this, transactionOptions.getReadTime())); + } else { + // For READ_ONLY transactions without readTime, there is still strong consistency applied, + // that cannot be tracked client side. + result = new ServerSideTransactionRunner<>(this, updateFunction, transactionOptions).run(); + } + metricsContext.recordLatencyAtFuture(MetricType.END_TO_END_LATENCY, result); + } catch (Exception error) { + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); + throw error; } + return result; } @Nonnull diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FirestoreOpenTelemetryOptions.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FirestoreOpenTelemetryOptions.java index d82a710523..6fe0c4375a 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FirestoreOpenTelemetryOptions.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FirestoreOpenTelemetryOptions.java @@ -27,23 +27,17 @@ */ @BetaApi public class FirestoreOpenTelemetryOptions { - private final boolean tracingEnabled; + private final boolean exportBuiltinMetricsToGoogleCloudMonitoring; private final @Nullable OpenTelemetry openTelemetry; FirestoreOpenTelemetryOptions(Builder builder) { - this.tracingEnabled = builder.tracingEnabled; + this.exportBuiltinMetricsToGoogleCloudMonitoring = + builder.exportBuiltinMetricsToGoogleCloudMonitoring; this.openTelemetry = builder.openTelemetry; } - /** - * @deprecated This method will be removed in the next minor version update. Please use a no-op - * TracerProvider or set the environment variable `FIRESTORE_ENABLE_TRACING=OFF` to disable - * tracing. If the GlobalOpenTelemetry or the OpenTelemetry instance passed to Firestore - * contain a valid TracerProvider, the Firestore client will generate spans by utilizing it. - */ - @Deprecated - public boolean isTracingEnabled() { - return tracingEnabled; + public boolean exportBuiltinMetricsToGoogleCloudMonitoring() { + return exportBuiltinMetricsToGoogleCloudMonitoring; } public OpenTelemetry getOpenTelemetry() { @@ -61,18 +55,18 @@ public static FirestoreOpenTelemetryOptions.Builder newBuilder() { } public static class Builder { - - private boolean tracingEnabled; - + private boolean exportBuiltinMetricsToGoogleCloudMonitoring; @Nullable private OpenTelemetry openTelemetry; private Builder() { - tracingEnabled = false; + // TODO(metrics): default this to true when feature is ready + exportBuiltinMetricsToGoogleCloudMonitoring = false; openTelemetry = null; } private Builder(FirestoreOpenTelemetryOptions options) { - this.tracingEnabled = options.tracingEnabled; + this.exportBuiltinMetricsToGoogleCloudMonitoring = + options.exportBuiltinMetricsToGoogleCloudMonitoring; this.openTelemetry = options.openTelemetry; } @@ -81,20 +75,17 @@ public FirestoreOpenTelemetryOptions build() { return new FirestoreOpenTelemetryOptions(this); } + // TODO(metrics): make this public when feature is ready. /** - * Sets whether tracing should be enabled. + * Sets whether built-in metrics should be exported to Google Cloud Monitoring * - * @param tracingEnabled Whether tracing should be enabled. - * @deprecated This method will be removed in the next minor version update. Please use a no-op - * TracerProvider or set the environment variable `FIRESTORE_ENABLE_TRACING=OFF` to disable - * tracing. If the GlobalOpenTelemetry or the OpenTelemetry instance passed to Firestore - * contains a valid TracerProvider, the Firestore client will generate spans by utilizing - * it. + * @param exportBuiltinMetrics Whether built-in metrics should be exported to Google Cloud + * Monitoring. */ - @Deprecated @Nonnull - public FirestoreOpenTelemetryOptions.Builder setTracingEnabled(boolean tracingEnabled) { - this.tracingEnabled = tracingEnabled; + private FirestoreOpenTelemetryOptions.Builder exportBuiltinMetricsToGoogleCloudMonitoring( + boolean exportBuiltinMetrics) { + this.exportBuiltinMetricsToGoogleCloudMonitoring = exportBuiltinMetrics; return this; } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FirestoreOptions.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FirestoreOptions.java index b80b9dacdc..b0887f602a 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FirestoreOptions.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FirestoreOptions.java @@ -24,12 +24,15 @@ import com.google.api.gax.core.GoogleCredentialsProvider; import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; import com.google.api.gax.rpc.TransportChannelProvider; +import com.google.api.gax.tracing.ApiTracerFactory; import com.google.auth.Credentials; import com.google.cloud.ServiceDefaults; import com.google.cloud.ServiceOptions; import com.google.cloud.TransportOptions; import com.google.cloud.firestore.spi.v1.FirestoreRpc; import com.google.cloud.firestore.spi.v1.GrpcFirestoreRpc; +import com.google.cloud.firestore.telemetry.CompositeApiTracerFactory; +import com.google.cloud.firestore.telemetry.MetricsUtil; import com.google.cloud.firestore.v1.FirestoreSettings; import com.google.cloud.grpc.GrpcTransportOptions; import com.google.common.collect.ImmutableMap; @@ -37,6 +40,7 @@ import io.grpc.ManagedChannelBuilder; import java.io.IOException; import java.net.URI; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -64,6 +68,7 @@ public final class FirestoreOptions extends ServiceOptions apiTracerFactories = new ArrayList<>(); + // Prefer any direct ApiTracerFactory that might have been set on the builder. + if (super.getApiTracerFactory() != null) { + apiTracerFactories.add(super.getApiTracerFactory()); + } + // Add Metrics Tracer factory if built-in metrics are enabled. + metricsUtil.addMetricsTracerFactory(apiTracerFactories); + + if (apiTracerFactories.isEmpty()) { + return null; + } + return new CompositeApiTracerFactory(apiTracerFactories); + } + public String getDatabaseId() { return databaseId; } @@ -127,6 +149,11 @@ com.google.cloud.firestore.telemetry.TraceUtil getTraceUtil() { return traceUtil; } + @Nonnull + com.google.cloud.firestore.telemetry.MetricsUtil getMetricsUtil() { + return metricsUtil; + } + @BetaApi @Nonnull public FirestoreOpenTelemetryOptions getOpenTelemetryOptions() { @@ -325,6 +352,9 @@ protected FirestoreOptions(Builder builder) { ? builder.databaseId : FirestoreDefaults.INSTANCE.getDatabaseId(); + // Set up the `MetricsUtil` instance after the database ID has been set. + this.metricsUtil = MetricsUtil.getInstance(this); + if (builder.channelProvider == null) { ApiFunction channelConfigurator = this.traceUtil.getChannelConfigurator(); diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java index e66393b848..f93ec2c29f 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java @@ -35,6 +35,8 @@ import com.google.cloud.Timestamp; import com.google.cloud.firestore.Query.QueryOptions.Builder; import com.google.cloud.firestore.encoding.CustomClassMapper; +import com.google.cloud.firestore.telemetry.MetricsUtil.MetricsContext; +import com.google.cloud.firestore.telemetry.TelemetryConstants; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.firestore.bundle.BundledQuery; @@ -1487,7 +1489,10 @@ public void stream(@Nonnull final ApiStreamObserver responseOb "Query results for queries that include limitToLast() constraints cannot be streamed. " + "Use Query.get() instead."); - internalStream( + MetricsContext metricsContext = + createMetricsContext(TelemetryConstants.METHOD_NAME_RUN_QUERY_GET); + + ApiStreamObserver observer = new ApiStreamObserver() { @Override public void onNext(RunQueryResponse runQueryResponse) { @@ -1509,7 +1514,10 @@ public void onError(Throwable throwable) { public void onCompleted() { responseObserver.onCompleted(); } - }, + }; + + internalStream( + new MonitoredStreamResponseObserver(observer, metricsContext), /* startTimeNanos= */ rpcContext.getClock().nanoTime(), /* transactionId= */ null, /* readTime= */ null, @@ -1533,8 +1541,12 @@ public ApiFuture explainStream( "Query results for queries that include limitToLast() constraints cannot be streamed. " + "Use Query.explain() instead."); + MetricsContext metricsContext = + createMetricsContext(TelemetryConstants.METHOD_NAME_RUN_QUERY_EXPLAIN); + final SettableApiFuture metricsFuture = SettableApiFuture.create(); - internalStream( + + ApiStreamObserver observer = new ApiStreamObserver() { @Override public void onNext(RunQueryResponse runQueryResponse) { @@ -1566,7 +1578,10 @@ public void onCompleted() { new RuntimeException("Did not receive any explain results.")); } } - }, + }; + + internalStream( + new MonitoredStreamResponseObserver(observer, metricsContext), /* startTimeNanos= */ rpcContext.getClock().nanoTime(), /* transactionId= */ null, /* readTime= */ null, diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ReadTimeTransaction.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ReadTimeTransaction.java index 0c423469af..8780e9c632 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ReadTimeTransaction.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ReadTimeTransaction.java @@ -18,6 +18,7 @@ import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; +import com.google.cloud.firestore.telemetry.TelemetryConstants; import com.google.cloud.firestore.telemetry.TraceUtil; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.MoreExecutors; @@ -57,7 +58,8 @@ public boolean hasTransactionId() { public ApiFuture get(@Nonnull DocumentReference documentRef) { TraceUtil.Span span = getTraceUtil() - .startSpan(TraceUtil.SPAN_NAME_TRANSACTION_GET_DOCUMENT, transactionTraceContext); + .startSpan( + TelemetryConstants.METHOD_NAME_TRANSACTION_GET_DOCUMENT, transactionTraceContext); try (TraceUtil.Scope ignored = span.makeCurrent()) { ApiFuture result = ApiFutures.transform( @@ -79,7 +81,8 @@ public ApiFuture> getAll( @Nonnull DocumentReference... documentReferences) { TraceUtil.Span span = getTraceUtil() - .startSpan(TraceUtil.SPAN_NAME_TRANSACTION_GET_DOCUMENTS, transactionTraceContext); + .startSpan( + TelemetryConstants.METHOD_NAME_TRANSACTION_GET_DOCUMENTS, transactionTraceContext); try (TraceUtil.Scope ignored = span.makeCurrent()) { ApiFuture> result = firestore.getAll(documentReferences, /* fieldMask= */ null, readTime); @@ -97,7 +100,8 @@ public ApiFuture> getAll( @Nonnull DocumentReference[] documentReferences, @Nullable FieldMask fieldMask) { TraceUtil.Span span = getTraceUtil() - .startSpan(TraceUtil.SPAN_NAME_TRANSACTION_GET_DOCUMENTS, transactionTraceContext); + .startSpan( + TelemetryConstants.METHOD_NAME_TRANSACTION_GET_DOCUMENTS, transactionTraceContext); try (TraceUtil.Scope ignored = span.makeCurrent()) { ApiFuture> result = firestore.getAll(documentReferences, /* fieldMask= */ null, readTime); diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ServerSideTransaction.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ServerSideTransaction.java index 5d366c965f..40dd64ce88 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ServerSideTransaction.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ServerSideTransaction.java @@ -19,6 +19,7 @@ import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; import com.google.cloud.firestore.TransactionOptions.TransactionOptionsType; +import com.google.cloud.firestore.telemetry.TelemetryConstants; import com.google.cloud.firestore.telemetry.TraceUtil; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.MoreExecutors; @@ -108,7 +109,9 @@ ApiFuture> commit() { /** Rolls a transaction back and releases all read locks. */ ApiFuture rollback() { TraceUtil.Span span = - getTraceUtil().startSpan(TraceUtil.SPAN_NAME_TRANSACTION_ROLLBACK, transactionTraceContext); + getTraceUtil() + .startSpan( + TelemetryConstants.METHOD_NAME_TRANSACTION_ROLLBACK, transactionTraceContext); try (TraceUtil.Scope ignored = span.makeCurrent()) { RollbackRequest req = RollbackRequest.newBuilder() @@ -158,7 +161,8 @@ public boolean hasTransactionId() { public ApiFuture get(@Nonnull DocumentReference documentRef) { TraceUtil.Span span = getTraceUtil() - .startSpan(TraceUtil.SPAN_NAME_TRANSACTION_GET_DOCUMENT, transactionTraceContext); + .startSpan( + TelemetryConstants.METHOD_NAME_TRANSACTION_GET_DOCUMENT, transactionTraceContext); try (TraceUtil.Scope ignored = span.makeCurrent()) { Preconditions.checkState(isEmpty(), READ_BEFORE_WRITE_ERROR_MSG); ApiFuture result = @@ -191,7 +195,8 @@ public ApiFuture> getAll( Preconditions.checkState(isEmpty(), READ_BEFORE_WRITE_ERROR_MSG); TraceUtil.Span span = getTraceUtil() - .startSpan(TraceUtil.SPAN_NAME_TRANSACTION_GET_DOCUMENTS, transactionTraceContext); + .startSpan( + TelemetryConstants.METHOD_NAME_TRANSACTION_GET_DOCUMENTS, transactionTraceContext); try (TraceUtil.Scope ignored = span.makeCurrent()) { ApiFuture> result = firestore.getAll( @@ -219,7 +224,8 @@ public ApiFuture> getAll( Preconditions.checkState(isEmpty(), READ_BEFORE_WRITE_ERROR_MSG); TraceUtil.Span span = getTraceUtil() - .startSpan(TraceUtil.SPAN_NAME_TRANSACTION_GET_DOCUMENTS, transactionTraceContext); + .startSpan( + TelemetryConstants.METHOD_NAME_TRANSACTION_GET_DOCUMENTS, transactionTraceContext); try (TraceUtil.Scope ignored = span.makeCurrent()) { ApiFuture> result = firestore.getAll(documentReferences, fieldMask, transactionId, /* readTime= */ null); diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ServerSideTransactionRunner.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ServerSideTransactionRunner.java index db8ebff635..29c92331a3 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ServerSideTransactionRunner.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/ServerSideTransactionRunner.java @@ -26,6 +26,9 @@ import com.google.api.gax.retrying.ExponentialRetryAlgorithm; import com.google.api.gax.retrying.TimedAttemptSettings; import com.google.api.gax.rpc.ApiException; +import com.google.cloud.firestore.telemetry.MetricsUtil.MetricsContext; +import com.google.cloud.firestore.telemetry.TelemetryConstants; +import com.google.cloud.firestore.telemetry.TelemetryConstants.MetricType; import com.google.cloud.firestore.telemetry.TraceUtil; import com.google.cloud.firestore.telemetry.TraceUtil.Scope; import com.google.cloud.firestore.telemetry.TraceUtil.Span; @@ -59,6 +62,7 @@ final class ServerSideTransactionRunner { private int attemptsRemaining; private Span runTransactionSpan; private TraceUtil.Context runTransactionContext; + private MetricsContext metricsContext; /** * @param firestore The active Firestore instance @@ -85,6 +89,11 @@ final class ServerSideTransactionRunner { new ExponentialRetryAlgorithm( firestore.getOptions().getRetrySettings(), CurrentMillisClock.getDefaultClock()); this.nextBackoffAttempt = backoffAlgorithm.createFirstAttempt(); + this.metricsContext = + firestore + .getOptions() + .getMetricsUtil() + .createMetricsContext(TelemetryConstants.METHOD_NAME_TRANSACTION_RUN); } @Nonnull @@ -93,7 +102,14 @@ private TraceUtil getTraceUtil() { } ApiFuture run() { - runTransactionSpan = getTraceUtil().startSpan(TraceUtil.SPAN_NAME_TRANSACTION_RUN); + ApiFuture result = runInternally(); + metricsContext.recordLatencyAtFuture(MetricType.TRANSACTION_LATENCY, result); + metricsContext.recordCounterAtFuture(MetricType.TRANSACTION_ATTEMPT_COUNT, result); + return result; + } + + ApiFuture runInternally() { + runTransactionSpan = getTraceUtil().startSpan(TelemetryConstants.METHOD_NAME_TRANSACTION_RUN); runTransactionSpan.setAttribute( ATTRIBUTE_KEY_TRANSACTION_TYPE, transactionOptions.getType().name()); runTransactionSpan.setAttribute( @@ -102,6 +118,7 @@ ApiFuture run() { try (Scope ignored = runTransactionSpan.makeCurrent()) { runTransactionContext = getTraceUtil().currentContext(); --attemptsRemaining; + metricsContext.incrementCounter(); ApiFuture result = ApiFutures.catchingAsync( ApiFutures.transformAsync( @@ -119,7 +136,8 @@ ApiFuture run() { ApiFuture begin() { TraceUtil.Span span = - getTraceUtil().startSpan(TraceUtil.SPAN_NAME_TRANSACTION_BEGIN, runTransactionContext); + getTraceUtil() + .startSpan(TelemetryConstants.METHOD_NAME_TRANSACTION_BEGIN, runTransactionContext); try (Scope ignored = span.makeCurrent()) { ServerSideTransaction previousTransaction = this.transaction; this.transaction = null; @@ -235,7 +253,7 @@ private ApiFuture restartTransactionCallback(Throwable throwable) { getTraceUtil() .currentSpan() .addEvent("Initiating transaction retry. Attempts remaining: " + attemptsRemaining); - return run(); + return runInternally(); } else { final FirestoreException firestoreException = FirestoreException.forApiException( diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/StreamableQuery.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/StreamableQuery.java index bf66a41abf..a959f9a153 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/StreamableQuery.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/StreamableQuery.java @@ -20,13 +20,18 @@ import static com.google.common.collect.Lists.reverse; import com.google.api.core.ApiFuture; +import com.google.api.core.InternalApi; import com.google.api.core.SettableApiFuture; import com.google.api.gax.rpc.ApiStreamObserver; import com.google.api.gax.rpc.ResponseObserver; import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.StreamController; import com.google.cloud.Timestamp; +import com.google.cloud.firestore.telemetry.MetricsUtil.MetricsContext; +import com.google.cloud.firestore.telemetry.TelemetryConstants; +import com.google.cloud.firestore.telemetry.TelemetryConstants.MetricType; import com.google.cloud.firestore.telemetry.TraceUtil; +import com.google.cloud.firestore.telemetry.TraceUtil.Scope; import com.google.cloud.firestore.v1.FirestoreSettings; import com.google.common.collect.ImmutableMap; import com.google.firestore.v1.Document; @@ -49,6 +54,7 @@ * `isRetryableWithCursor`. Retrying with a cursor means that the StreamableQuery can be resumed * where it failed by first calling `startAfter(lastDocumentReceived)`. */ +@InternalApi public abstract class StreamableQuery { final Query.QueryOptions options; final FirestoreRpcContext rpcContext; @@ -79,6 +85,10 @@ abstract SnapshotType createSnaphot( public Firestore getFirestore() { return rpcContext.getFirestore(); } + + MetricsContext createMetricsContext(String methodName) { + return getFirestore().getOptions().getMetricsUtil().createMetricsContext(methodName); + } /** * Executes the query and returns the results as QuerySnapshot. * @@ -100,11 +110,19 @@ ApiFuture get( .getTraceUtil() .startSpan( transactionId == null - ? TraceUtil.SPAN_NAME_QUERY_GET - : TraceUtil.SPAN_NAME_TRANSACTION_GET_QUERY); + ? TelemetryConstants.METHOD_NAME_QUERY_GET + : TelemetryConstants.METHOD_NAME_TRANSACTION_GET_QUERY); + + MetricsContext metricsContext = + createMetricsContext( + transactionId != null + ? TelemetryConstants.METHOD_NAME_RUN_QUERY_TRANSACTIONAL + : TelemetryConstants.METHOD_NAME_RUN_QUERY_GET); + try (Scope ignored = span.makeCurrent()) { final SettableApiFuture result = SettableApiFuture.create(); - internalStream( + + ApiStreamObserver observer = new ApiStreamObserver() { final List documentSnapshots = new ArrayList<>(); Timestamp responseReadTime; @@ -139,7 +157,10 @@ public void onCompleted() { SnapshotType querySnapshot = createSnaphot(responseReadTime, resultView); result.set(querySnapshot); } - }, + }; + + internalStream( + new MonitoredStreamResponseObserver(observer, metricsContext), /* startTimeNanos= */ rpcContext.getClock().nanoTime(), transactionId, /* readTime= */ requestReadTime, @@ -150,6 +171,7 @@ public void onCompleted() { return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } @@ -165,11 +187,18 @@ public void onCompleted() { @Nonnull public ApiFuture> explain(ExplainOptions options) { TraceUtil.Span span = - getFirestore().getOptions().getTraceUtil().startSpan(TraceUtil.SPAN_NAME_QUERY_GET); + getFirestore() + .getOptions() + .getTraceUtil() + .startSpan(TelemetryConstants.METHOD_NAME_QUERY_GET); + + MetricsContext metricsContext = + createMetricsContext(TelemetryConstants.METHOD_NAME_RUN_QUERY_EXPLAIN); try (Scope ignored = span.makeCurrent()) { final SettableApiFuture> result = SettableApiFuture.create(); - internalStream( + + ApiStreamObserver observer = new ApiStreamObserver() { @Nullable List documentSnapshots = null; Timestamp readTime; @@ -222,7 +251,10 @@ public void onCompleted() { } result.set(new ExplainResults<>(metrics, snapshot)); } - }, + }; + + internalStream( + new MonitoredStreamResponseObserver(observer, metricsContext), /* startTimeNanos= */ rpcContext.getClock().nanoTime(), /* transactionId= */ null, /* readTime= */ null, @@ -233,25 +265,61 @@ public void onCompleted() { return result; } catch (Exception error) { span.end(error); + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, error); throw error; } } - protected void internalStream( - final ApiStreamObserver runQueryResponseObserver, + class MonitoredStreamResponseObserver implements ApiStreamObserver { + private final ApiStreamObserver observer; + private final MetricsContext metricsContext; + private boolean receivedFirstResponse = false; + + // Constructor to initialize with the delegate and MetricsContext + public MonitoredStreamResponseObserver( + ApiStreamObserver observer, MetricsContext metricsContext) { + this.observer = observer; + this.metricsContext = metricsContext; + } + + @Override + public void onNext(RunQueryResponse value) { + if (!receivedFirstResponse) { + receivedFirstResponse = true; + metricsContext.recordLatency(MetricType.FIRST_RESPONSE_LATENCY); + } + observer.onNext(value); + } + + @Override + public void onError(Throwable t) { + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY, t); + observer.onError(t); + } + + @Override + public void onCompleted() { + metricsContext.recordLatency(MetricType.END_TO_END_LATENCY); + observer.onCompleted(); + } + } + + void internalStream( + final MonitoredStreamResponseObserver streamResponseObserver, final long startTimeNanos, @Nullable final ByteString transactionId, @Nullable final Timestamp readTime, @Nullable final ExplainOptions explainOptions, final boolean isRetryRequestWithCursor) { TraceUtil traceUtil = getFirestore().getOptions().getTraceUtil(); + // To reduce the size of traces, we only register one event for every 100 responses // that we receive from the server. final int NUM_RESPONSES_PER_TRACE_EVENT = 100; TraceUtil.Span currentSpan = traceUtil.currentSpan(); currentSpan.addEvent( - TraceUtil.SPAN_NAME_RUN_QUERY, + TelemetryConstants.METHOD_NAME_RUN_QUERY, new ImmutableMap.Builder() .put(ATTRIBUTE_KEY_IS_TRANSACTIONAL, transactionId != null) .put(ATTRIBUTE_KEY_IS_RETRY_WITH_CURSOR, isRetryRequestWithCursor) @@ -276,16 +344,19 @@ public void onStart(StreamController streamController) {} public void onResponse(RunQueryResponse response) { if (!firstResponse) { firstResponse = true; - currentSpan.addEvent(TraceUtil.SPAN_NAME_RUN_QUERY + ": First Response"); + currentSpan.addEvent(TelemetryConstants.METHOD_NAME_RUN_QUERY + ": First Response"); } - runQueryResponseObserver.onNext(response); + streamResponseObserver.onNext(response); if (response.hasDocument()) { numDocuments++; if (numDocuments % NUM_RESPONSES_PER_TRACE_EVENT == 0) { currentSpan.addEvent( - TraceUtil.SPAN_NAME_RUN_QUERY + ": Received " + numDocuments + " documents"); + TelemetryConstants.METHOD_NAME_RUN_QUERY + + ": Received " + + numDocuments + + " documents"); } Document document = response.getDocument(); QueryDocumentSnapshot documentSnapshot = @@ -296,7 +367,7 @@ public void onResponse(RunQueryResponse response) { if (response.getDone()) { currentSpan.addEvent( - TraceUtil.SPAN_NAME_RUN_QUERY + ": Received RunQueryResponse.Done"); + TelemetryConstants.METHOD_NAME_RUN_QUERY + ": Received RunQueryResponse.Done"); onComplete(); } } @@ -306,12 +377,12 @@ public void onError(Throwable throwable) { QueryDocumentSnapshot cursor = lastReceivedDocument.get(); if (isRetryableWithCursor() && shouldRetry(cursor, throwable)) { currentSpan.addEvent( - TraceUtil.SPAN_NAME_RUN_QUERY + ": Retryable Error", - Collections.singletonMap("error.message", throwable.getMessage())); + TelemetryConstants.METHOD_NAME_RUN_QUERY + ": Retryable Error", + Collections.singletonMap("error.message", throwable.toString())); startAfter(cursor) .internalStream( - runQueryResponseObserver, + streamResponseObserver, startTimeNanos, /* transactionId= */ null, options.getRequireConsistency() ? cursor.getReadTime() : null, @@ -319,9 +390,9 @@ public void onError(Throwable throwable) { /* isRetryRequestWithCursor= */ true); } else { currentSpan.addEvent( - TraceUtil.SPAN_NAME_RUN_QUERY + ": Error", - Collections.singletonMap("error.message", throwable.getMessage())); - runQueryResponseObserver.onError(throwable); + TelemetryConstants.METHOD_NAME_RUN_QUERY + ": Error", + Collections.singletonMap("error.message", throwable.toString())); + streamResponseObserver.onError(throwable); } } @@ -330,9 +401,9 @@ public void onComplete() { if (hasCompleted) return; hasCompleted = true; currentSpan.addEvent( - TraceUtil.SPAN_NAME_RUN_QUERY + ": Completed", + TelemetryConstants.METHOD_NAME_RUN_QUERY + ": Completed", Collections.singletonMap(ATTRIBUTE_KEY_DOC_COUNT, numDocuments)); - runQueryResponseObserver.onCompleted(); + streamResponseObserver.onCompleted(); } boolean shouldRetry(DocumentSnapshot lastDocument, Throwable t) { diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Transaction.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Transaction.java index 04d83a1a12..ff1b7e2d67 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Transaction.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Transaction.java @@ -18,6 +18,7 @@ import com.google.api.core.ApiFuture; import com.google.api.core.InternalExtensionOnly; +import com.google.cloud.firestore.telemetry.MetricsUtil; import com.google.cloud.firestore.telemetry.TraceUtil; import com.google.cloud.firestore.telemetry.TraceUtil.Context; import java.util.List; @@ -49,6 +50,12 @@ TraceUtil getTraceUtil() { return firestore.getOptions().getTraceUtil(); } + // TODO(Metrics): implement transaction latency and attempt count metrics + @Nonnull + MetricsUtil getMetricsUtil() { + return firestore.getOptions().getMetricsUtil(); + } + @Nonnull Context setTransactionTraceContext(Context context) { return transactionTraceContext = context; diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java index 31434667ba..cfa852ce4c 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UpdateBuilder.java @@ -26,6 +26,7 @@ import com.google.api.core.InternalExtensionOnly; import com.google.cloud.firestore.UserDataConverter.EncodingOptions; import com.google.cloud.firestore.encoding.CustomClassMapper; +import com.google.cloud.firestore.telemetry.TelemetryConstants; import com.google.cloud.firestore.telemetry.TraceUtil; import com.google.cloud.firestore.telemetry.TraceUtil.Scope; import com.google.common.base.Preconditions; @@ -616,8 +617,8 @@ ApiFuture> commit(@Nullable ByteString transactionId) { .getTraceUtil() .startSpan( transactionId == null - ? TraceUtil.SPAN_NAME_BATCH_COMMIT - : TraceUtil.SPAN_NAME_TRANSACTION_COMMIT); + ? TelemetryConstants.METHOD_NAME_BATCH_COMMIT + : TelemetryConstants.METHOD_NAME_TRANSACTION_COMMIT); span.setAttribute(ATTRIBUTE_KEY_DOC_COUNT, writes.size()); span.setAttribute(ATTRIBUTE_KEY_IS_TRANSACTIONAL, transactionId != null); try (Scope ignored = span.makeCurrent()) { diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/spi/v1/GrpcFirestoreRpc.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/spi/v1/GrpcFirestoreRpc.java index c391e9b801..9668fbeb09 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/spi/v1/GrpcFirestoreRpc.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/spi/v1/GrpcFirestoreRpc.java @@ -28,6 +28,7 @@ import com.google.api.gax.rpc.ServerStreamingCallable; import com.google.api.gax.rpc.TransportChannel; import com.google.api.gax.rpc.UnaryCallable; +import com.google.api.gax.tracing.ApiTracerFactory; import com.google.cloud.NoCredentials; import com.google.cloud.ServiceOptions; import com.google.cloud.firestore.FirestoreOptions; @@ -157,6 +158,11 @@ public GrpcFirestoreRpc(final FirestoreOptions options) throws IOException { firestoreBuilder.batchGetDocumentsSettings().setRetrySettings(retrySettings); } + ApiTracerFactory apiTracerFactory = options.getApiTracerFactory(); + if (apiTracerFactory != null) { + firestoreBuilder.setTracerFactory(apiTracerFactory); + } + firestoreStub = GrpcFirestoreStub.create(firestoreBuilder.build()); } catch (Exception e) { throw new IOException(e); diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/BuiltinMetricsProvider.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/BuiltinMetricsProvider.java new file mode 100644 index 0000000000..621efb914c --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/BuiltinMetricsProvider.java @@ -0,0 +1,181 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.telemetry; + +import static com.google.cloud.firestore.telemetry.TelemetryConstants.FIRESTORE_METER_NAME; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METRIC_ATTRIBUTE_KEY_CLIENT_UID; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METRIC_ATTRIBUTE_KEY_LIBRARY_NAME; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METRIC_ATTRIBUTE_KEY_LIBRARY_VERSION; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METRIC_NAME_END_TO_END_LATENCY; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METRIC_NAME_FIRST_RESPONSE_LATENCY; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METRIC_NAME_TRANSACTION_ATTEMPT_COUNT; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METRIC_NAME_TRANSACTION_LATENCY; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METRIC_PREFIX; + +import com.google.api.gax.tracing.ApiTracerFactory; +import com.google.api.gax.tracing.MetricsTracerFactory; +import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder; +import com.google.cloud.firestore.telemetry.TelemetryConstants.MetricType; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterProvider; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A provider for built-in metrics. This class is responsible for storing OpenTelemetry metrics + * configuration and recording built-in metrics for the Firestore SDK. + */ +class BuiltinMetricsProvider { + private static final Logger logger = Logger.getLogger(BuiltinMetricsProvider.class.getName()); + + private OpenTelemetry openTelemetry; + private DoubleHistogram endToEndLatency; + private DoubleHistogram firstResponseLatency; + private DoubleHistogram transactionLatency; + private LongCounter transactionAttemptCount; + + private ApiTracerFactory apiTracerFactory; + private final Map staticAttributes; + + private static final String MILLISECOND_UNIT = "ms"; + private static final String INTEGER_UNIT = "1"; + private static final String FIRESTORE_LIBRARY_NAME = "com.google.cloud.firestore"; + + public BuiltinMetricsProvider(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + this.staticAttributes = createStaticAttributes(); + + if (openTelemetry.getMeterProvider() != MeterProvider.noop()) { + configureRPCLayerMetrics(); + configureSDKLayerMetrics(); + } + } + + private Map createStaticAttributes() { + Map staticAttributes = new HashMap<>(); + staticAttributes.put(METRIC_ATTRIBUTE_KEY_CLIENT_UID.getKey(), ClientIdentifier.getClientUid()); + staticAttributes.put(METRIC_ATTRIBUTE_KEY_LIBRARY_NAME.getKey(), FIRESTORE_LIBRARY_NAME); + String pkgVersion = this.getClass().getPackage().getImplementationVersion(); + if (pkgVersion != null) { + staticAttributes.put(METRIC_ATTRIBUTE_KEY_LIBRARY_VERSION.getKey(), pkgVersion); + } + return staticAttributes; + } + + /** Creates an ApiTracerFactory to be passed into GAX library and collect RPC layer metrics. */ + private void configureRPCLayerMetrics() { + OpenTelemetryMetricsRecorder recorder = + new OpenTelemetryMetricsRecorder(openTelemetry, METRIC_PREFIX); + this.apiTracerFactory = new MetricsTracerFactory(recorder, staticAttributes); + } + + /** Registers metrics to be collected at the Firestore SDK layer */ + private void configureSDKLayerMetrics() { + Meter meter = openTelemetry.getMeter(FIRESTORE_METER_NAME); + + this.endToEndLatency = + meter + .histogramBuilder(METRIC_PREFIX + "/" + METRIC_NAME_END_TO_END_LATENCY) + .setDescription("Firestore operations' end-to-end latency") + .setUnit(MILLISECOND_UNIT) + .build(); + + this.firstResponseLatency = + meter + .histogramBuilder(METRIC_PREFIX + "/" + METRIC_NAME_FIRST_RESPONSE_LATENCY) + .setDescription("Firestore streaming operations' first response latency") + .setUnit(MILLISECOND_UNIT) + .build(); + + this.transactionLatency = + meter + .histogramBuilder(METRIC_PREFIX + "/" + METRIC_NAME_TRANSACTION_LATENCY) + .setDescription("Firestore transactions' end-to-end latency") + .setUnit(MILLISECOND_UNIT) + .build(); + + this.transactionAttemptCount = + meter + .counterBuilder(METRIC_PREFIX + "/" + METRIC_NAME_TRANSACTION_ATTEMPT_COUNT) + .setDescription("Number of Firestore transaction attempts including retries") + .setUnit(INTEGER_UNIT) + .build(); + } + + public ApiTracerFactory getApiTracerFactory() { + return this.apiTracerFactory; + } + + public void latencyRecorder( + MetricType metricType, double latency, Map attributes) { + DoubleHistogram histogram = getHistogram(metricType); + if (histogram != null) { + attributes.putAll(staticAttributes); + try { + histogram.record(latency, toOtelAttributes(attributes)); + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to record latency metric: " + e.getMessage(), e); + } + } + } + + public void counterRecorder(MetricType metricType, long count, Map attributes) { + LongCounter counter = getCounter(metricType); + if (counter != null) { + attributes.putAll(staticAttributes); + try { + counter.add(count, toOtelAttributes(attributes)); + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to record counter metric:" + e.getMessage(), e); + } + } + } + + public DoubleHistogram getHistogram(MetricType metricType) { + switch (metricType) { + case END_TO_END_LATENCY: + return endToEndLatency; + case FIRST_RESPONSE_LATENCY: + return firstResponseLatency; + case TRANSACTION_LATENCY: + return transactionLatency; + default: + throw new IllegalArgumentException("Unknown latency MetricType: " + metricType); + } + } + + public LongCounter getCounter(MetricType metricType) { + if (metricType == MetricType.TRANSACTION_ATTEMPT_COUNT) { + return transactionAttemptCount; + } else { + throw new IllegalArgumentException("Unknown counter MetricType: " + metricType); + } + } + + private Attributes toOtelAttributes(Map attributes) { + AttributesBuilder attributesBuilder = Attributes.builder(); + attributes.forEach(attributesBuilder::put); + return attributesBuilder.build(); + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/ClientIdentifier.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/ClientIdentifier.java new file mode 100644 index 0000000000..dc4a3eae05 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/ClientIdentifier.java @@ -0,0 +1,77 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.telemetry; + +import java.lang.management.ManagementFactory; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.util.UUID; + +/** A utility class for retrieving a unique client identifier (CLIENT_UID) */ +final class ClientIdentifier { + + private ClientIdentifier() {} + + private static String CLIENT_UID; + + /** Gets the unique identifier for the client. */ + public static String getClientUid() { + if (CLIENT_UID == null) { + CLIENT_UID = generateClientUid(); + } + return CLIENT_UID; + } + + /** + * Generates a unique identifier for the client that is composed of a random UUID, the process ID, + * and the hostname of the machine. + */ + private static String generateClientUid() { + String identifier = UUID.randomUUID().toString(); + String pid = getProcessId(); + String hostname = getHostName(); + return String.format("%s@%s@%s", identifier, pid, hostname); + } + + private static String getHostName() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (Exception e) { + return "localhost"; + } + } + + private static String getProcessId() { + try { + // Check if Java 9+ and ProcessHandle class is available + Class processHandleClass = Class.forName("java.lang.ProcessHandle"); + Method currentMethod = processHandleClass.getMethod("current"); + Object processHandleInstance = currentMethod.invoke(null); + Method pidMethod = processHandleClass.getMethod("pid"); + long pid = (long) pidMethod.invoke(processHandleInstance); + return Long.toString(pid); + } catch (Exception e) { + // Fallback to Java 8 method + final String jvmName = ManagementFactory.getRuntimeMXBean().getName(); + if (jvmName != null && jvmName.contains("@")) { + return jvmName.split("@")[0]; + } else { + return "unknown"; + } + } + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/CompositeApiTracer.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/CompositeApiTracer.java new file mode 100644 index 0000000000..31dbddbbf3 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/CompositeApiTracer.java @@ -0,0 +1,129 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.telemetry; + +import com.google.api.gax.tracing.ApiTracer; +import com.google.api.gax.tracing.BaseApiTracer; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.threeten.bp.Duration; + +/** Combines multiple {@link ApiTracer}s into a single {@link ApiTracer}. */ +class CompositeApiTracer extends BaseApiTracer { + private final List children; + + public CompositeApiTracer(List children) { + this.children = ImmutableList.copyOf(children); + } + + @Override + public Scope inScope() { + List childScopes = + children.stream() + .map(ApiTracer::inScope) + .collect(Collectors.toCollection(() -> new ArrayList<>(children.size()))); + + return () -> childScopes.forEach(Scope::close); + } + + @Override + public void operationSucceeded() { + children.forEach(ApiTracer::operationSucceeded); + } + + @Override + public void operationCancelled() { + children.forEach(ApiTracer::operationCancelled); + } + + @Override + public void operationFailed(Throwable error) { + children.forEach(child -> child.operationFailed(error)); + } + + @Override + public void connectionSelected(String id) { + children.forEach(child -> child.connectionSelected(id)); + } + + @Override + public void attemptStarted(int attemptNumber) { + children.forEach(child -> child.attemptStarted(null, attemptNumber)); + } + + @Override + public void attemptStarted(Object request, int attemptNumber) { + children.forEach(child -> child.attemptStarted(request, attemptNumber)); + } + + @Override + public void attemptSucceeded() { + children.forEach(ApiTracer::attemptSucceeded); + } + + @Override + public void attemptCancelled() { + children.forEach(ApiTracer::attemptCancelled); + } + + @Override + public void attemptFailed(Throwable error, Duration delay) { + children.forEach(child -> child.attemptFailed(error, delay)); + } + + @Override + public void attemptFailedDuration(Throwable error, java.time.Duration delay) { + children.forEach(child -> child.attemptFailedDuration(error, delay)); + } + + @Override + public void attemptFailedRetriesExhausted(Throwable error) { + children.forEach(child -> child.attemptFailedRetriesExhausted(error)); + } + + @Override + public void attemptPermanentFailure(Throwable error) { + children.forEach(child -> child.attemptPermanentFailure(error)); + } + + @Override + public void lroStartFailed(Throwable error) { + children.forEach(child -> child.lroStartFailed(error)); + } + + @Override + public void lroStartSucceeded() { + children.forEach(ApiTracer::lroStartSucceeded); + } + + @Override + public void responseReceived() { + children.forEach(ApiTracer::responseReceived); + } + + @Override + public void requestSent() { + children.forEach(ApiTracer::requestSent); + } + + @Override + public void batchRequestSent(long elementCount, long requestSize) { + children.forEach(child -> child.batchRequestSent(elementCount, requestSize)); + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/CompositeApiTracerFactory.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/CompositeApiTracerFactory.java new file mode 100644 index 0000000000..ddd0f226d1 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/CompositeApiTracerFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.telemetry; + +import com.google.api.core.InternalApi; +import com.google.api.gax.tracing.ApiTracer; +import com.google.api.gax.tracing.ApiTracerFactory; +import com.google.api.gax.tracing.BaseApiTracerFactory; +import com.google.api.gax.tracing.SpanName; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.List; + +/** Combines multiple {@link ApiTracerFactory} into a single {@link ApiTracerFactory}. */ +@InternalApi +public class CompositeApiTracerFactory extends BaseApiTracerFactory { + + private final List apiTracerFactories; + + public CompositeApiTracerFactory(List apiTracerFactories) { + this.apiTracerFactories = ImmutableList.copyOf(apiTracerFactories); + } + + @Override + public ApiTracer newTracer(ApiTracer parent, SpanName spanName, OperationType operationType) { + List children = new ArrayList<>(apiTracerFactories.size()); + + for (ApiTracerFactory factory : apiTracerFactories) { + children.add(factory.newTracer(parent, spanName, operationType)); + } + return new CompositeApiTracer(children); + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/DisabledMetricsUtil.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/DisabledMetricsUtil.java new file mode 100644 index 0000000000..0642033c00 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/DisabledMetricsUtil.java @@ -0,0 +1,55 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.telemetry; + +import com.google.api.core.ApiFuture; +import com.google.api.gax.tracing.ApiTracerFactory; +import com.google.cloud.firestore.telemetry.TelemetryConstants.MetricType; +import java.util.List; + +/** + * A fully disabled (No-op) MetricsUtil class that does not perform any metrics collection actions + * and has near-zero overhead. + */ +class DisabledMetricsUtil implements MetricsUtil { + + class MetricsContext implements MetricsUtil.MetricsContext { + + @Override + public void recordLatencyAtFuture(MetricType metric, ApiFuture futureValue) {} + + @Override + public void recordLatency(MetricType metric) {} + + @Override + public void recordLatency(MetricType metric, Throwable t) {} + + @Override + public void recordCounterAtFuture(MetricType metric, ApiFuture futureValue) {} + + @Override + public void incrementCounter() {} + } + + @Override + public MetricsContext createMetricsContext(String methodName) { + return new MetricsContext(); + } + + @Override + public void addMetricsTracerFactory(List apiTracerFactories) {} +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/DisabledTraceUtil.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/DisabledTraceUtil.java index c2b47d536f..16428b8cfd 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/DisabledTraceUtil.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/DisabledTraceUtil.java @@ -18,15 +18,17 @@ import com.google.api.core.ApiFunction; import com.google.api.core.ApiFuture; +import com.google.api.core.InternalApi; import io.grpc.ManagedChannelBuilder; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** - * A fully disabled (No-op) tracing utility class that does not perform any tracing actions and has + * A no-op implementation of {@link MetricsUtil} that does not collect or export any metrics and has * near-zero overhead. */ +@InternalApi public class DisabledTraceUtil implements TraceUtil { static class Span implements TraceUtil.Span { diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/EnabledMetricsUtil.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/EnabledMetricsUtil.java new file mode 100644 index 0000000000..5a9bd4d7b6 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/EnabledMetricsUtil.java @@ -0,0 +1,262 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.telemetry; + +import static com.google.cloud.firestore.telemetry.TelemetryConstants.COMMON_ATTRIBUTES; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.FIRESTORE_METER_NAME; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.FIRESTORE_METRICS; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.GAX_METER_NAME; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.GAX_METRICS; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METRIC_ATTRIBUTE_KEY_METHOD; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METRIC_ATTRIBUTE_KEY_STATUS; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METRIC_PREFIX; + +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutureCallback; +import com.google.api.core.ApiFutures; +import com.google.api.gax.rpc.StatusCode; +import com.google.api.gax.tracing.ApiTracerFactory; +import com.google.cloud.firestore.FirestoreException; +import com.google.cloud.firestore.FirestoreOptions; +import com.google.cloud.firestore.telemetry.TelemetryConstants.MetricType; +import com.google.cloud.opentelemetry.metric.GoogleCloudMetricExporter; +import com.google.cloud.opentelemetry.metric.MetricConfiguration; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.MoreExecutors; +import io.grpc.Status; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.InstrumentSelector; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; +import io.opentelemetry.sdk.metrics.View; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +/** + * An implementation of {@link MetricsUtil} that uses OpenTelemetry to collect and export metrics. + * `FirestoreOpenTelemetryOptions` in `FirestoreOptions` can be used to configure its behavior. + */ +class EnabledMetricsUtil implements MetricsUtil { + private BuiltinMetricsProvider defaultMetricsProvider; + private BuiltinMetricsProvider customMetricsProvider; + + private static final Logger logger = Logger.getLogger(EnabledMetricsUtil.class.getName()); + + EnabledMetricsUtil(FirestoreOptions firestoreOptions) { + try { + configureDefaultMetricsProvider(firestoreOptions); + configureCustomMetricsProvider(firestoreOptions); + } catch (IOException e) { + logger.warning( + "Unable to create MetricsUtil object for client side metrics, will skip exporting client side metrics" + + e); + } + } + + private void configureDefaultMetricsProvider(FirestoreOptions firestoreOptions) + throws IOException { + OpenTelemetry defaultOpenTelemetry; + boolean exportBuiltinMetricsToGoogleCloudMonitoring = + firestoreOptions.getOpenTelemetryOptions().exportBuiltinMetricsToGoogleCloudMonitoring(); + if (exportBuiltinMetricsToGoogleCloudMonitoring) { + defaultOpenTelemetry = getDefaultOpenTelemetryInstance(firestoreOptions.getProjectId()); + } else { + defaultOpenTelemetry = OpenTelemetry.noop(); + } + this.defaultMetricsProvider = new BuiltinMetricsProvider(defaultOpenTelemetry); + } + + private void configureCustomMetricsProvider(FirestoreOptions firestoreOptions) + throws IOException { + OpenTelemetry customOpenTelemetry = + firestoreOptions.getOpenTelemetryOptions().getOpenTelemetry(); + if (customOpenTelemetry == null) { + customOpenTelemetry = GlobalOpenTelemetry.get(); + } + this.customMetricsProvider = new BuiltinMetricsProvider(customOpenTelemetry); + } + + @Override + public MetricsContext createMetricsContext(String methodName) { + return new MetricsContext(methodName); + } + + @Override + public void addMetricsTracerFactory(List apiTracerFactories) { + addTracerFactory(apiTracerFactories, defaultMetricsProvider); + addTracerFactory(apiTracerFactories, customMetricsProvider); + } + + /** + * Creates a default {@link OpenTelemetry} instance to collect and export built-in client side + * metrics to Google Cloud Monitoring. + */ + private OpenTelemetry getDefaultOpenTelemetryInstance(String projectId) throws IOException { + SdkMeterProviderBuilder sdkMeterProviderBuilder = SdkMeterProvider.builder(); + + // Filter out attributes that are not defined + for (Map.Entry entry : getAllViews().entrySet()) { + sdkMeterProviderBuilder.registerView(entry.getKey(), entry.getValue()); + } + + MetricExporter metricExporter = + GoogleCloudMetricExporter.createWithConfiguration( + MetricConfiguration.builder() + .setProjectId(projectId) + // Ignore library info as it is collected by the metric attributes as well + .setInstrumentationLibraryLabelsEnabled(false) + .build()); + sdkMeterProviderBuilder.registerMetricReader(PeriodicMetricReader.create(metricExporter)); + + return OpenTelemetrySdk.builder().setMeterProvider(sdkMeterProviderBuilder.build()).build(); + } + + private static Map getAllViews() { + ImmutableMap.Builder views = ImmutableMap.builder(); + GAX_METRICS.forEach(metric -> defineView(views, metric, GAX_METER_NAME)); + FIRESTORE_METRICS.forEach(metric -> defineView(views, metric, FIRESTORE_METER_NAME)); + return views.build(); + } + + private static void defineView( + ImmutableMap.Builder viewMap, String id, String meter) { + InstrumentSelector selector = + InstrumentSelector.builder().setMeterName(meter).setName(METRIC_PREFIX + "/" + id).build(); + Set attributesFilter = + ImmutableSet.builder() + .addAll( + COMMON_ATTRIBUTES.stream().map(AttributeKey::getKey).collect(Collectors.toSet())) + .build(); + View view = View.builder().setAttributeFilter(attributesFilter).build(); + + viewMap.put(selector, view); + } + + private void addTracerFactory( + List apiTracerFactories, BuiltinMetricsProvider metricsProvider) { + ApiTracerFactory tracerFactory = metricsProvider.getApiTracerFactory(); + if (tracerFactory != null) { + apiTracerFactories.add(tracerFactory); + } + } + + class MetricsContext implements MetricsUtil.MetricsContext { + private final Stopwatch stopwatch; + private int counter; + protected final String methodName; + + public MetricsContext(String methodName) { + this.stopwatch = Stopwatch.createStarted(); + this.methodName = methodName; + this.counter = 0; + } + + public void recordLatencyAtFuture(MetricType metric, ApiFuture futureValue) { + ApiFutures.addCallback( + futureValue, + new ApiFutureCallback() { + @Override + public void onFailure(Throwable t) { + recordLatency(metric, t); + } + + @Override + public void onSuccess(T result) { + recordLatency(metric); + } + }, + MoreExecutors.directExecutor()); + } + + public void recordLatency(MetricType metric) { + recordLatency(metric, StatusCode.Code.OK.toString()); + } + + public void recordLatency(MetricType metric, Throwable t) { + recordLatency(metric, extractErrorStatus(t)); + } + + private void recordLatency(MetricType metric, String status) { + double elapsedTime = stopwatch.elapsed(TimeUnit.MILLISECONDS); + Map attributes = createAttributes(status, methodName); + defaultMetricsProvider.latencyRecorder(metric, elapsedTime, attributes); + customMetricsProvider.latencyRecorder(metric, elapsedTime, attributes); + } + + public void incrementCounter() { + counter++; + } + + public void recordCounterAtFuture(MetricType metric, ApiFuture futureValue) { + ApiFutures.addCallback( + futureValue, + new ApiFutureCallback() { + @Override + public void onFailure(Throwable t) { + recordCounter(metric, extractErrorStatus(t)); + } + + @Override + public void onSuccess(T result) { + recordCounter(metric, StatusCode.Code.OK.toString()); + } + }, + MoreExecutors.directExecutor()); + } + + private void recordCounter(MetricType metric, String status) { + Map attributes = createAttributes(status, methodName); + defaultMetricsProvider.counterRecorder( + MetricType.TRANSACTION_ATTEMPT_COUNT, (long) counter, attributes); + customMetricsProvider.counterRecorder( + MetricType.TRANSACTION_ATTEMPT_COUNT, (long) counter, attributes); + } + } + + private Map createAttributes(String status, String methodName) { + Map attributes = new HashMap<>(); + attributes.put(METRIC_ATTRIBUTE_KEY_METHOD.getKey(), methodName); + attributes.put(METRIC_ATTRIBUTE_KEY_STATUS.getKey(), status); + return attributes; + } + + private String extractErrorStatus(@Nullable Throwable throwable) { + if (!(throwable instanceof FirestoreException)) { + return StatusCode.Code.UNKNOWN.toString(); + } + + Status status = ((FirestoreException) throwable).getStatus(); + if (status == null) { + return StatusCode.Code.UNKNOWN.toString(); + } + return status.getCode().name(); + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/EnabledTraceUtil.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/EnabledTraceUtil.java index 52bb7c5e08..b0eeeca218 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/EnabledTraceUtil.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/EnabledTraceUtil.java @@ -20,6 +20,7 @@ import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutureCallback; import com.google.api.core.ApiFutures; +import com.google.api.core.InternalApi; import com.google.cloud.firestore.FirestoreOptions; import com.google.common.base.Throwables; import io.grpc.ManagedChannelBuilder; @@ -31,6 +32,7 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTelemetry; import java.util.Map; import javax.annotation.Nonnull; @@ -40,6 +42,7 @@ * A utility class that uses OpenTelemetry for trace collection. `FirestoreOpenTelemetryOptions` in * `FirestoreOptions` can be used to configure its behavior. */ +@InternalApi public class EnabledTraceUtil implements TraceUtil { private final Tracer tracer; private final OpenTelemetry openTelemetry; @@ -48,8 +51,7 @@ public class EnabledTraceUtil implements TraceUtil { EnabledTraceUtil(FirestoreOptions firestoreOptions) { OpenTelemetry openTelemetry = firestoreOptions.getOpenTelemetryOptions().getOpenTelemetry(); - // If tracing is enabled, but an OpenTelemetry instance is not provided, fall back - // to using GlobalOpenTelemetry. + // If an OpenTelemetry instance is not provided, fall back to using GlobalOpenTelemetry. if (openTelemetry == null) { openTelemetry = GlobalOpenTelemetry.get(); } @@ -84,6 +86,11 @@ public ManagedChannelBuilder apply(ManagedChannelBuilder managedChannelBuilder) @Override @Nullable public ApiFunction getChannelConfigurator() { + // Note: using `==` rather than `.equals` since OpenTelemetry has only 1 static instance of + // `TracerProvider.noop`. + if (openTelemetry.getTracerProvider() == TracerProvider.noop()) { + return null; + } return new OpenTelemetryGrpcChannelConfigurator(); } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/MetricsUtil.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/MetricsUtil.java new file mode 100644 index 0000000000..02ac3a26be --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/MetricsUtil.java @@ -0,0 +1,119 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.telemetry; + +import com.google.api.core.ApiFuture; +import com.google.api.core.InternalApi; +import com.google.api.gax.tracing.ApiTracerFactory; +import com.google.cloud.firestore.FirestoreOptions; +import com.google.cloud.firestore.telemetry.TelemetryConstants.MetricType; +import java.util.List; +import java.util.logging.Logger; +import javax.annotation.Nonnull; + +/** + * A utility interface for metrics collection. Classes that implement this interface may make their + * own design choices for how they approach metrics collection. For instance, they may be no-op, or + * they may use a particular metrics framework such as OpenTelemetry. + */ +@InternalApi +public interface MetricsUtil { + final Logger logger = Logger.getLogger(MetricsUtil.class.getName()); + + static final String ENABLE_METRICS_ENV_VAR = "FIRESTORE_ENABLE_METRICS"; + + /** + * Creates an instance of {@code MetricsUtil}. If the environment variable + * `FIRESTORE_ENABLE_METRICS` is set to false or off, an instance of {@link DisabledMetricsUtil} + * will be returned. Otherwise, an instance of {@link EnabledMetricsUtil} will be returned. + * + * @param firestoreOptions The Firestore options that configures client side metrics. + * @return An instance of {@code MetricsUtil}. + */ + static MetricsUtil getInstance(@Nonnull FirestoreOptions firestoreOptions) { + if (shouldCreateEnabledInstance()) { + return new EnabledMetricsUtil(firestoreOptions); + } else { + return new DisabledMetricsUtil(); + } + } + + static boolean shouldCreateEnabledInstance() { + // Client side metrics feature is default on unless it is manually turned off by + // environment variables + // TODO(metrics): The feature is disabled before it is ready for general release. + boolean shouldCreateEnabledInstance = false; + + String enableMetricsEnvVar = System.getenv(ENABLE_METRICS_ENV_VAR); + if (enableMetricsEnvVar != null) { + switch (enableMetricsEnvVar.toLowerCase()) { + case "true": + case "on": + // The feature is default on. + break; + case "false": + case "off": + shouldCreateEnabledInstance = false; + break; + default: + logger.warning("Invalid value for FIRESTORE_ENABLE_METRICS: " + enableMetricsEnvVar); + } + } + + return shouldCreateEnabledInstance; + } + + /** + * Creates a new {@code MetricsContext} for the given method and starts timing. + * + * @param methodName The name of the method. + * @return A new {@code MetricsContext}. + */ + abstract MetricsContext createMetricsContext(String methodName); + + /** + * Adds a metrics tracer factory to the given list of API tracer factories. + * + * @param apiTracerFactories The list of API tracer factories. + */ + abstract void addMetricsTracerFactory(List apiTracerFactories); + + /** A context for recording metrics in the Firestore SDK. */ + interface MetricsContext { + /** + * If the operation ends in the future, its relevant metrics should be recorded _after_ the + * future has been completed. This method "appends" the metrics recording code at the completion + * of the given future. + */ + void recordLatencyAtFuture(MetricType metric, ApiFuture futureValue); + + /** Records specific type of latency for the current operation. */ + void recordLatency(MetricType metric); + + /** Records specific type of latency for the current operation, which ended with a throwable. */ + void recordLatency(MetricType metric, Throwable t); + + /** + * Records the counter value for a metric type _after_ the future has been completed. This + * method "appends" the metrics recording code at the completion of the given future. + */ + void recordCounterAtFuture(MetricType metric, ApiFuture futureValue); + + /** Increments the counter tracked inside the MetricsContext. */ + void incrementCounter(); + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/TelemetryConstants.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/TelemetryConstants.java new file mode 100644 index 0000000000..d2ab8bf173 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/TelemetryConstants.java @@ -0,0 +1,124 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.firestore.telemetry; + +import com.google.api.core.InternalApi; +import com.google.api.gax.tracing.OpenTelemetryMetricsRecorder; +import com.google.common.collect.ImmutableSet; +import io.opentelemetry.api.common.AttributeKey; +import java.util.Set; + +/** Constants used for telemetry in the Firestore SDK. */ +@InternalApi +public interface TelemetryConstants { + // Method names for Firestore operations + String METHOD_NAME_DOC_REF_CREATE = "DocumentReference.Create"; + String METHOD_NAME_DOC_REF_SET = "DocumentReference.Set"; + String METHOD_NAME_DOC_REF_UPDATE = "DocumentReference.Update"; + String METHOD_NAME_DOC_REF_DELETE = "DocumentReference.Delete"; + String METHOD_NAME_DOC_REF_GET = "DocumentReference.Get"; + String METHOD_NAME_DOC_REF_LIST_COLLECTIONS = "DocumentReference.ListCollections"; + String METHOD_NAME_COL_REF_ADD = "CollectionReference.Add"; + String METHOD_NAME_COL_REF_LIST_DOCUMENTS = "CollectionReference.ListDocuments"; + String METHOD_NAME_QUERY_GET = "Query.Get"; + String METHOD_NAME_AGGREGATION_QUERY_GET = "AggregationQuery.Get"; + String METHOD_NAME_RUN_QUERY = "RunQuery"; + String METHOD_NAME_RUN_QUERY_EXPLAIN = "RunQuery.Explain"; + String METHOD_NAME_RUN_QUERY_GET = "RunQuery.Get"; + String METHOD_NAME_RUN_QUERY_TRANSACTIONAL = "RunQuery.Transactional"; + String METHOD_NAME_RUN_AGGREGATION_QUERY = "RunAggregationQuery"; + String METHOD_NAME_RUN_AGGREGATION_QUERY_EXPLAIN = "RunAggregationQuery.Explain"; + String METHOD_NAME_RUN_AGGREGATION_QUERY_GET = "RunAggregationQuery.Get"; + String METHOD_NAME_RUN_AGGREGATION_QUERY_TRANSACTIONAL = "RunAggregationQuery.Transactional"; + String METHOD_NAME_BATCH_GET_DOCUMENTS = "BatchGetDocuments"; + String METHOD_NAME_BATCH_GET_DOCUMENTS_GET_ALL = "BatchGetDocuments.GetAll"; + String METHOD_NAME_BATCH_GET_DOCUMENTS_TRANSACTIONAL = "BatchGetDocuments.Transactional"; + String METHOD_NAME_TRANSACTION_RUN = "Transaction.Run"; + String METHOD_NAME_TRANSACTION_BEGIN = "Transaction.Begin"; + String METHOD_NAME_TRANSACTION_GET_QUERY = "Transaction.Get.Query"; + String METHOD_NAME_TRANSACTION_GET_AGGREGATION_QUERY = "Transaction.Get.AggregationQuery"; + String METHOD_NAME_TRANSACTION_GET_DOCUMENT = "Transaction.Get.Document"; + String METHOD_NAME_TRANSACTION_GET_DOCUMENTS = "Transaction.Get.Documents"; + String METHOD_NAME_TRANSACTION_ROLLBACK = "Transaction.Rollback"; + String METHOD_NAME_BATCH_COMMIT = "Batch.Commit"; + String METHOD_NAME_TRANSACTION_COMMIT = "Transaction.Commit"; + String METHOD_NAME_PARTITION_QUERY = "PartitionQuery"; + String METHOD_NAME_BULK_WRITER_COMMIT = "BulkWriter.Commit"; + String METHOD_NAME_RUN_TRANSACTION = "RunTransaction"; + + // OpenTelemetry built-in metrics constants + String FIRESTORE_RESOURCE_TYPE = "firestore_client_raw"; + // TODO(metrics): change to firestore.googleapis.com + String METRIC_PREFIX = "custom.googleapis.com/internal/client"; + String FIRESTORE_METER_NAME = "java_firestore"; + String GAX_METER_NAME = OpenTelemetryMetricsRecorder.GAX_METER_NAME; + + // Monitored resource keys for labels + String RESOURCE_KEY_RESOURCE_CONTAINER = "resource_container"; + String RESOURCE_KEY_LOCATION = "location"; + String RESOURCE_KEY_DATABASE_ID = "database_id"; + Set FIRESTORE_RESOURCE_LABELS = + ImmutableSet.of( + RESOURCE_KEY_RESOURCE_CONTAINER, RESOURCE_KEY_LOCATION, RESOURCE_KEY_DATABASE_ID); + + // Metric attribute keys for labels + AttributeKey METRIC_ATTRIBUTE_KEY_METHOD = AttributeKey.stringKey("method"); + AttributeKey METRIC_ATTRIBUTE_KEY_STATUS = AttributeKey.stringKey("status"); + AttributeKey METRIC_ATTRIBUTE_KEY_LIBRARY_NAME = AttributeKey.stringKey("library_name"); + AttributeKey METRIC_ATTRIBUTE_KEY_LIBRARY_VERSION = + AttributeKey.stringKey("library_version"); + AttributeKey METRIC_ATTRIBUTE_KEY_CLIENT_UID = AttributeKey.stringKey("client_uid"); + Set COMMON_ATTRIBUTES = + ImmutableSet.of( + METRIC_ATTRIBUTE_KEY_CLIENT_UID, + METRIC_ATTRIBUTE_KEY_LIBRARY_NAME, + METRIC_ATTRIBUTE_KEY_LIBRARY_VERSION, + METRIC_ATTRIBUTE_KEY_STATUS, + METRIC_ATTRIBUTE_KEY_METHOD); + + // Metric names + String METRIC_NAME_OPERATION_LATENCY = "operation_latency"; + String METRIC_NAME_OPERATION_COUNT = "operation_count"; + String METRIC_NAME_ATTEMPT_LATENCY = "attempt_latency"; + String METRIC_NAME_ATTEMPT_COUNT = "attempt_count"; + String METRIC_NAME_FIRST_RESPONSE_LATENCY = "first_response_latency"; + String METRIC_NAME_END_TO_END_LATENCY = "end_to_end_latency"; + String METRIC_NAME_TRANSACTION_LATENCY = "transaction_latency"; + String METRIC_NAME_TRANSACTION_ATTEMPT_COUNT = "transaction_attempt_count"; + + // Metrics collected on GAX and Firestore SDK layer + Set GAX_METRICS = + ImmutableSet.of( + METRIC_NAME_OPERATION_LATENCY, + METRIC_NAME_ATTEMPT_LATENCY, + METRIC_NAME_OPERATION_COUNT, + METRIC_NAME_ATTEMPT_COUNT); + + Set FIRESTORE_METRICS = + ImmutableSet.of( + METRIC_NAME_FIRST_RESPONSE_LATENCY, + METRIC_NAME_END_TO_END_LATENCY, + METRIC_NAME_TRANSACTION_LATENCY, + METRIC_NAME_TRANSACTION_ATTEMPT_COUNT); + + public enum MetricType { + END_TO_END_LATENCY, + FIRST_RESPONSE_LATENCY, + TRANSACTION_LATENCY, + TRANSACTION_ATTEMPT_COUNT + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/TraceUtil.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/TraceUtil.java index 22dfb3ed0c..847eff58ac 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/TraceUtil.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/telemetry/TraceUtil.java @@ -18,6 +18,7 @@ import com.google.api.core.ApiFunction; import com.google.api.core.ApiFuture; +import com.google.api.core.InternalApi; import com.google.cloud.firestore.FirestoreOptions; import io.grpc.ManagedChannelBuilder; import java.util.Map; @@ -31,30 +32,6 @@ */ public interface TraceUtil { String ATTRIBUTE_SERVICE_PREFIX = "gcp.firestore."; - String SPAN_NAME_DOC_REF_CREATE = "DocumentReference.Create"; - String SPAN_NAME_DOC_REF_SET = "DocumentReference.Set"; - String SPAN_NAME_DOC_REF_UPDATE = "DocumentReference.Update"; - String SPAN_NAME_DOC_REF_DELETE = "DocumentReference.Delete"; - String SPAN_NAME_DOC_REF_GET = "DocumentReference.Get"; - String SPAN_NAME_DOC_REF_LIST_COLLECTIONS = "DocumentReference.ListCollections"; - String SPAN_NAME_COL_REF_ADD = "CollectionReference.Add"; - String SPAN_NAME_COL_REF_LIST_DOCUMENTS = "CollectionReference.ListDocuments"; - String SPAN_NAME_QUERY_GET = "Query.Get"; - String SPAN_NAME_AGGREGATION_QUERY_GET = "AggregationQuery.Get"; - String SPAN_NAME_RUN_QUERY = "RunQuery"; - String SPAN_NAME_RUN_AGGREGATION_QUERY = "RunAggregationQuery"; - String SPAN_NAME_BATCH_GET_DOCUMENTS = "BatchGetDocuments"; - String SPAN_NAME_TRANSACTION_RUN = "Transaction.Run"; - String SPAN_NAME_TRANSACTION_BEGIN = "Transaction.Begin"; - String SPAN_NAME_TRANSACTION_GET_QUERY = "Transaction.Get.Query"; - String SPAN_NAME_TRANSACTION_GET_AGGREGATION_QUERY = "Transaction.Get.AggregationQuery"; - String SPAN_NAME_TRANSACTION_GET_DOCUMENT = "Transaction.Get.Document"; - String SPAN_NAME_TRANSACTION_GET_DOCUMENTS = "Transaction.Get.Documents"; - String SPAN_NAME_TRANSACTION_ROLLBACK = "Transaction.Rollback"; - String SPAN_NAME_BATCH_COMMIT = "Batch.Commit"; - String SPAN_NAME_TRANSACTION_COMMIT = "Transaction.Commit"; - String SPAN_NAME_PARTITION_QUERY = "PartitionQuery"; - String SPAN_NAME_BULK_WRITER_COMMIT = "BulkWriter.Commit"; String ATTRIBUTE_KEY_ATTEMPT = "attempt"; String ATTRIBUTE_KEY_DOC_COUNT = "doc_count"; String ATTRIBUTE_KEY_IS_TRANSACTIONAL = "transactional"; @@ -74,8 +51,9 @@ public interface TraceUtil { * TraceUtil. * @return An instance of the TraceUtil class. */ + @InternalApi static TraceUtil getInstance(@Nonnull FirestoreOptions firestoreOptions) { - boolean createEnabledInstance = firestoreOptions.getOpenTelemetryOptions().isTracingEnabled(); + boolean createEnabledInstance = true; // The environment variable can override options to enable/disable telemetry collection. String enableTracingEnvVar = System.getenv(ENABLE_TRACING_ENV_VAR); diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/OpenTelemetryOptionsTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/OpenTelemetryOptionsTest.java index 267020761c..009e2457b8 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/OpenTelemetryOptionsTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/OpenTelemetryOptionsTest.java @@ -18,11 +18,12 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.cloud.firestore.telemetry.DisabledTraceUtil; import com.google.cloud.firestore.telemetry.EnabledTraceUtil; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; import javax.annotation.Nullable; import org.junit.After; import org.junit.Before; @@ -49,112 +50,67 @@ FirestoreOptions.Builder getBaseOptions() { } @Test - public void defaultOptionsDisablesTelemetryCollection() { + public void defaultOptionsUsesEnabledTraceUtilWithNoopOpenTelemetry() { FirestoreOptions firestoreOptions = getBaseOptions().build(); firestore = firestoreOptions.getService(); - assertThat(firestore.getOptions().getOpenTelemetryOptions().isTracingEnabled()).isFalse(); assertThat(firestore.getOptions().getOpenTelemetryOptions().getOpenTelemetry()).isNull(); assertThat(firestore.getOptions().getTraceUtil()).isNotNull(); - assertThat(firestore.getOptions().getTraceUtil() instanceof DisabledTraceUtil).isTrue(); - } - - @Test - public void canEnableTelemetryCollectionWithoutOpenTelemetryInstance() { - FirestoreOptions firestoreOptions = - getBaseOptions() - .setOpenTelemetryOptions( - FirestoreOpenTelemetryOptions.newBuilder().setTracingEnabled(true).build()) - .build(); - firestore = firestoreOptions.getService(); - assertThat(firestore.getOptions().getOpenTelemetryOptions().isTracingEnabled()).isTrue(); - assertThat(firestore.getOptions().getOpenTelemetryOptions().getOpenTelemetry()).isNull(); - assertThat(firestore.getOptions().getTraceUtil()).isNotNull(); - assertThat(firestore.getOptions().getTraceUtil() instanceof EnabledTraceUtil).isTrue(); - } - - @Test - public void canEnableTelemetryCollectionWithOpenTelemetryInstance() { - OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); - FirestoreOptions firestoreOptions = - getBaseOptions() - .setOpenTelemetryOptions( - FirestoreOpenTelemetryOptions.newBuilder() - .setTracingEnabled(true) - .setOpenTelemetry(openTelemetry) - .build()) - .build(); - firestore = firestoreOptions.getService(); - assertThat(firestore.getOptions().getOpenTelemetryOptions().isTracingEnabled()).isTrue(); - assertThat(firestore.getOptions().getOpenTelemetryOptions().getOpenTelemetry()) - .isEqualTo(openTelemetry); - assertThat(firestore.getOptions().getTraceUtil()).isNotNull(); assertThat(firestore.getOptions().getTraceUtil() instanceof EnabledTraceUtil).isTrue(); + EnabledTraceUtil enabledTraceUtil = (EnabledTraceUtil) firestore.getOptions().getTraceUtil(); + // Assert that a Noop tracer provider is used by default. + assertThat(enabledTraceUtil.getOpenTelemetry().getTracerProvider()) + .isSameInstanceAs(TracerProvider.noop()); } @Test - public void canDisableTelemetryCollectionWhileOpenTelemetryInstanceIsNotNull() { - OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); - FirestoreOptions firestoreOptions = - getBaseOptions() - .setOpenTelemetryOptions( - FirestoreOpenTelemetryOptions.newBuilder() - .setTracingEnabled(false) - .setOpenTelemetry(openTelemetry) - .build()) - .build(); - firestore = firestoreOptions.getService(); - assertThat(firestore.getOptions().getOpenTelemetryOptions().isTracingEnabled()).isFalse(); - assertThat(firestore.getOptions().getOpenTelemetryOptions().getOpenTelemetry()) - .isEqualTo(openTelemetry); - assertThat(firestore.getOptions().getTraceUtil()).isNotNull(); - assertThat(firestore.getOptions().getTraceUtil() instanceof DisabledTraceUtil).isTrue(); - } + public void existenceOfGlobalOpenTelemetryEnablesTracingWithTheGlobalTracerProvider() { + // Make a custom TracerProvider. + Resource resource = + Resource.getDefault().merge(Resource.builder().put("service.name", "test").build()); + SdkTracerProvider myTracerProvider = SdkTracerProvider.builder().setResource(resource).build(); - @Test - public void existenceOfGlobalOpenTelemetryDoesNotEnableTracing() { - // Register a global OpenTelemetry SDK. - OpenTelemetrySdk.builder().buildAndRegisterGlobal(); + // Register a GlobalOpenTelemetry with the custom tracer provider. + OpenTelemetrySdk.builder().setTracerProvider(myTracerProvider).buildAndRegisterGlobal(); - // Make sure Firestore does not use GlobalOpenTelemetry by default. + // Do NOT pass an OpenTelemetry instance to Firestore. FirestoreOptions firestoreOptions = getBaseOptions().build(); firestore = firestoreOptions.getService(); - assertThat(firestore.getOptions().getOpenTelemetryOptions().isTracingEnabled()).isFalse(); + + // An OpenTelemetry instance has not been set in options. assertThat(firestore.getOptions().getOpenTelemetryOptions().getOpenTelemetry()).isNull(); + + // Assert that tracing is enabled and is using the custom tracer provider from the + // GlobalOpenTelemetry. assertThat(firestore.getOptions().getTraceUtil()).isNotNull(); - assertThat(firestore.getOptions().getTraceUtil() instanceof DisabledTraceUtil).isTrue(); + assertThat(firestore.getOptions().getTraceUtil() instanceof EnabledTraceUtil).isTrue(); + EnabledTraceUtil enabledTraceUtil = (EnabledTraceUtil) firestore.getOptions().getTraceUtil(); + assertThat(enabledTraceUtil.getOpenTelemetry().getTracerProvider().equals(myTracerProvider)); } @Test public void canPassOpenTelemetrySdkInstanceToFirestore() { - OpenTelemetrySdk myOpenTelemetrySdk = OpenTelemetrySdk.builder().build(); + // Make a custom TracerProvider and make an OpenTelemetry instance with it. + Resource resource = + Resource.getDefault().merge(Resource.builder().put("service.name", "test").build()); + SdkTracerProvider myTracerProvider = SdkTracerProvider.builder().setResource(resource).build(); + OpenTelemetrySdk myOpenTelemetrySdk = + OpenTelemetrySdk.builder().setTracerProvider(myTracerProvider).build(); + + // Pass it to Firestore. FirestoreOptions firestoreOptions = getBaseOptions() .setOpenTelemetryOptions( FirestoreOpenTelemetryOptions.newBuilder() - .setTracingEnabled(true) .setOpenTelemetry(myOpenTelemetrySdk) .build()) .build(); firestore = firestoreOptions.getService(); + assertThat(firestore.getOptions().getOpenTelemetryOptions().getOpenTelemetry()) + .isEqualTo(myOpenTelemetrySdk); + assertThat(firestore.getOptions().getTraceUtil() instanceof EnabledTraceUtil).isTrue(); EnabledTraceUtil enabledTraceUtil = (EnabledTraceUtil) firestore.getOptions().getTraceUtil(); assertThat(enabledTraceUtil).isNotNull(); assertThat(enabledTraceUtil.getOpenTelemetry()).isEqualTo(myOpenTelemetrySdk); - } - - @Test - public void usesGlobalOpenTelemetryIfOpenTelemetryNotProvidedInOptions() { - // Register a global OpenTelemetry SDK. - OpenTelemetrySdk.builder().buildAndRegisterGlobal(); - - // Do _not_ pass it to FirestoreOptions. - FirestoreOptions firestoreOptions = - getBaseOptions() - .setOpenTelemetryOptions( - FirestoreOpenTelemetryOptions.newBuilder().setTracingEnabled(true).build()) - .build(); - firestore = firestoreOptions.getService(); - EnabledTraceUtil enabledTraceUtil = (EnabledTraceUtil) firestore.getOptions().getTraceUtil(); - assertThat(enabledTraceUtil).isNotNull(); - assertThat(enabledTraceUtil.getOpenTelemetry()).isEqualTo(GlobalOpenTelemetry.get()); + assertThat(enabledTraceUtil.getOpenTelemetry().getTracerProvider().equals(myTracerProvider)); } } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WatchTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WatchTest.java index 19295b9018..d875c6411b 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WatchTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/WatchTest.java @@ -24,11 +24,11 @@ import static com.google.cloud.firestore.LocalFirestoreHelper.UPDATED_FIELD_PROTO; import static com.google.cloud.firestore.LocalFirestoreHelper.map; import static com.google.cloud.firestore.LocalFirestoreHelper.string; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.*; import com.google.api.gax.grpc.GrpcStatusCode; @@ -169,10 +169,11 @@ public void before() { @After public void after() { - assertTrue(exceptions.isEmpty()); - assertTrue(requests.isEmpty()); - assertTrue(documentSnapshots.isEmpty()); - assertTrue(querySnapshots.isEmpty()); + Object[] emptyArray = new Object[0]; + assertArrayEquals(exceptions.toArray(), emptyArray); + assertArrayEquals(requests.toArray(), emptyArray); + assertArrayEquals(documentSnapshots.toArray(), emptyArray); + assertArrayEquals(querySnapshots.toArray(), emptyArray); listenerRegistration.remove(); } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITE2ETracingTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITE2ETracingTest.java index 3d385e344f..82dbe99146 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITE2ETracingTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITE2ETracingTest.java @@ -16,24 +16,24 @@ package com.google.cloud.firestore.it; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_BATCH_COMMIT; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_BULK_WRITER_COMMIT; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_COL_REF_LIST_DOCUMENTS; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_DOC_REF_CREATE; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_DOC_REF_DELETE; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_DOC_REF_GET; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_DOC_REF_LIST_COLLECTIONS; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_DOC_REF_SET; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_DOC_REF_UPDATE; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_PARTITION_QUERY; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_QUERY_GET; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_TRANSACTION_BEGIN; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_TRANSACTION_COMMIT; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_TRANSACTION_GET_AGGREGATION_QUERY; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_TRANSACTION_GET_DOCUMENTS; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_TRANSACTION_GET_QUERY; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_TRANSACTION_ROLLBACK; -import static com.google.cloud.firestore.telemetry.TraceUtil.SPAN_NAME_TRANSACTION_RUN; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_BATCH_COMMIT; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_BULK_WRITER_COMMIT; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_COL_REF_LIST_DOCUMENTS; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_DOC_REF_CREATE; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_DOC_REF_DELETE; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_DOC_REF_GET; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_DOC_REF_LIST_COLLECTIONS; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_DOC_REF_SET; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_DOC_REF_UPDATE; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_PARTITION_QUERY; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_QUERY_GET; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_TRANSACTION_BEGIN; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_TRANSACTION_COMMIT; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_TRANSACTION_GET_AGGREGATION_QUERY; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_TRANSACTION_GET_DOCUMENTS; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_TRANSACTION_GET_QUERY; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_TRANSACTION_ROLLBACK; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.METHOD_NAME_TRANSACTION_RUN; import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -319,20 +319,13 @@ public void before() throws Exception { // Initialize the Firestore DB w/ the OTel SDK. Ideally we'd do this is the @BeforeAll method // but because gRPC traces need to be deterministically force-flushed, firestore.shutdown() // must be called in @After for each test. - FirestoreOptions.Builder optionsBuilder; - if (isUsingGlobalOpenTelemetrySDK()) { - optionsBuilder = - FirestoreOptions.newBuilder() - .setOpenTelemetryOptions( - FirestoreOpenTelemetryOptions.newBuilder().setTracingEnabled(true).build()); - } else { + FirestoreOptions.Builder optionsBuilder = FirestoreOptions.newBuilder(); + if (!isUsingGlobalOpenTelemetrySDK()) { optionsBuilder = - FirestoreOptions.newBuilder() - .setOpenTelemetryOptions( - FirestoreOpenTelemetryOptions.newBuilder() - .setOpenTelemetry(openTelemetrySdk) - .setTracingEnabled(true) - .build()); + optionsBuilder.setOpenTelemetryOptions( + FirestoreOpenTelemetryOptions.newBuilder() + .setOpenTelemetry(openTelemetrySdk) + .build()); } String namedDb = System.getProperty("FIRESTORE_NAMED_DATABASE"); @@ -559,18 +552,18 @@ public void traceContainerTest() throws Exception { // Contains exact path assertTrue( traceCont.containsCallStack( - rootSpanName, SPAN_NAME_QUERY_GET, grpcSpanName(RUN_QUERY_RPC_NAME))); + rootSpanName, METHOD_NAME_QUERY_GET, grpcSpanName(RUN_QUERY_RPC_NAME))); // Top-level mismatch - assertFalse(traceCont.containsCallStack(SPAN_NAME_QUERY_GET, RUN_QUERY_RPC_NAME)); + assertFalse(traceCont.containsCallStack(METHOD_NAME_QUERY_GET, RUN_QUERY_RPC_NAME)); // Mid-level match - assertFalse(traceCont.containsCallStack(rootSpanName, SPAN_NAME_QUERY_GET)); + assertFalse(traceCont.containsCallStack(rootSpanName, METHOD_NAME_QUERY_GET)); // Leaf-level mismatch/missing assertFalse( traceCont.containsCallStack( - rootSpanName, SPAN_NAME_QUERY_GET, RUN_AGGREGATION_QUERY_RPC_NAME)); + rootSpanName, METHOD_NAME_QUERY_GET, RUN_AGGREGATION_QUERY_RPC_NAME)); } @Test @@ -621,7 +614,7 @@ public void bulkWriterCommitTraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_BULK_WRITER_COMMIT, + METHOD_NAME_BULK_WRITER_COMMIT, grpcSpanName(BATCH_WRITE_RPC_NAME)); } @@ -643,8 +636,8 @@ public void partitionQueryTraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_PARTITION_QUERY, - grpcSpanName(SPAN_NAME_PARTITION_QUERY)); + METHOD_NAME_PARTITION_QUERY, + grpcSpanName(METHOD_NAME_PARTITION_QUERY)); } @Test @@ -664,7 +657,7 @@ public void collectionListDocumentsTraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_COL_REF_LIST_DOCUMENTS, + METHOD_NAME_COL_REF_LIST_DOCUMENTS, grpcSpanName(LIST_DOCUMENTS_RPC_NAME)); } @@ -685,8 +678,8 @@ public void docRefCreateTraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_CREATE, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_CREATE, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -707,8 +700,8 @@ public void docRefCreate2TraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_CREATE, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_CREATE, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -729,8 +722,8 @@ public void docRefSetTraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_SET, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_SET, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -755,8 +748,8 @@ public void docRefSet2TraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_SET, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_SET, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -777,8 +770,8 @@ public void docRefSet3TraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_SET, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_SET, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -799,8 +792,8 @@ public void docRefSet4TraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_SET, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_SET, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -825,8 +818,8 @@ public void docRefUpdateTraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_UPDATE, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_UPDATE, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -851,8 +844,8 @@ public void docRefUpdate2TraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_UPDATE, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_UPDATE, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -873,8 +866,8 @@ public void docRefUpdate3TraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_UPDATE, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_UPDATE, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -899,8 +892,8 @@ public void docRefUpdate4TraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_UPDATE, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_UPDATE, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -925,8 +918,8 @@ public void docRefUpdate5TraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_UPDATE, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_UPDATE, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -951,8 +944,8 @@ public void docRefUpdate6TraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_UPDATE, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_UPDATE, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -973,8 +966,8 @@ public void docRefDeleteTraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_DELETE, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_DELETE, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -995,8 +988,8 @@ public void docRefDelete2TraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_DELETE, - SPAN_NAME_BATCH_COMMIT, + METHOD_NAME_DOC_REF_DELETE, + METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @@ -1017,7 +1010,7 @@ public void docRefGetTraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_GET, + METHOD_NAME_DOC_REF_GET, grpcSpanName(BATCH_GET_DOCUMENTS_RPC_NAME)); } @@ -1038,7 +1031,7 @@ public void docRefGet2TraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_GET, + METHOD_NAME_DOC_REF_GET, grpcSpanName(BATCH_GET_DOCUMENTS_RPC_NAME)); } @@ -1059,7 +1052,7 @@ public void docListCollectionsTraceTest() throws Exception { // Read and validate traces fetchAndValidateTrace( customSpanContext.getTraceId(), - SPAN_NAME_DOC_REF_LIST_COLLECTIONS, + METHOD_NAME_DOC_REF_LIST_COLLECTIONS, grpcSpanName(LIST_COLLECTIONS_RPC_NAME)); } @@ -1099,7 +1092,7 @@ public void queryGetTraceTest() throws Exception { waitForTracesToComplete(); fetchAndValidateTrace( - customSpanContext.getTraceId(), SPAN_NAME_QUERY_GET, grpcSpanName(RUN_QUERY_RPC_NAME)); + customSpanContext.getTraceId(), METHOD_NAME_QUERY_GET, grpcSpanName(RUN_QUERY_RPC_NAME)); } @Test @@ -1144,28 +1137,28 @@ public void transactionTraceTest() throws Exception { /*numExpectedSpans=*/ 11, Arrays.asList( Arrays.asList( - SPAN_NAME_TRANSACTION_RUN, - SPAN_NAME_TRANSACTION_BEGIN, + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_BEGIN, grpcSpanName(BEGIN_TRANSACTION_RPC_NAME)), Arrays.asList( - SPAN_NAME_TRANSACTION_RUN, - SPAN_NAME_TRANSACTION_BEGIN, + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_BEGIN, grpcSpanName(BEGIN_TRANSACTION_RPC_NAME)), Arrays.asList( - SPAN_NAME_TRANSACTION_RUN, - SPAN_NAME_TRANSACTION_GET_QUERY, + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_GET_QUERY, grpcSpanName(RUN_QUERY_RPC_NAME)), Arrays.asList( - SPAN_NAME_TRANSACTION_RUN, - SPAN_NAME_TRANSACTION_GET_AGGREGATION_QUERY, + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_GET_AGGREGATION_QUERY, grpcSpanName(RUN_AGGREGATION_QUERY_RPC_NAME)), Arrays.asList( - SPAN_NAME_TRANSACTION_RUN, - SPAN_NAME_TRANSACTION_GET_DOCUMENTS, + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_GET_DOCUMENTS, grpcSpanName(BATCH_GET_DOCUMENTS_RPC_NAME)), Arrays.asList( - SPAN_NAME_TRANSACTION_RUN, - SPAN_NAME_TRANSACTION_COMMIT, + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_COMMIT, grpcSpanName(COMMIT_RPC_NAME)))); } @@ -1199,12 +1192,12 @@ public void transactionRollbackTraceTest() throws Exception { /*numExpectedSpans=*/ 5, Arrays.asList( Arrays.asList( - SPAN_NAME_TRANSACTION_RUN, - SPAN_NAME_TRANSACTION_BEGIN, + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_BEGIN, grpcSpanName(BEGIN_TRANSACTION_RPC_NAME)), Arrays.asList( - SPAN_NAME_TRANSACTION_RUN, - SPAN_NAME_TRANSACTION_ROLLBACK, + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_ROLLBACK, grpcSpanName(ROLLBACK_RPC_NAME)))); } @@ -1228,6 +1221,6 @@ public void writeBatchTraceTest() throws Exception { waitForTracesToComplete(); fetchAndValidateTrace( - customSpanContext.getTraceId(), SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + customSpanContext.getTraceId(), METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITTracingTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITTracingTest.java index 46e8294e1c..225ff01622 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITTracingTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITTracingTest.java @@ -16,6 +16,7 @@ package com.google.cloud.firestore.it; +import static com.google.cloud.firestore.telemetry.TelemetryConstants.*; import static com.google.cloud.firestore.telemetry.TraceUtil.*; import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; import static org.junit.Assert.assertEquals; @@ -125,11 +126,11 @@ public void before() { if (isUsingGlobalOpenTelemetrySDK()) { GlobalOpenTelemetry.resetForTest(); openTelemetrySdk = openTelemetrySdkBuilder.buildAndRegisterGlobal(); - optionsBuilder.setOpenTelemetryOptions(otelOptionsBuilder.setTracingEnabled(true).build()); + optionsBuilder.setOpenTelemetryOptions(otelOptionsBuilder.build()); } else { openTelemetrySdk = openTelemetrySdkBuilder.build(); optionsBuilder.setOpenTelemetryOptions( - otelOptionsBuilder.setTracingEnabled(true).setOpenTelemetry(openTelemetrySdk).build()); + otelOptionsBuilder.setOpenTelemetry(openTelemetrySdk).build()); } String namedDb = System.getProperty("FIRESTORE_NAMED_DATABASE"); @@ -365,8 +366,8 @@ public void aggregateQueryGet() throws Exception { List spans = inMemorySpanExporter.getFinishedSpanItems(); buildSpanMaps(spans); assertEquals(2, spans.size()); - SpanData getSpan = getSpanByName(SPAN_NAME_AGGREGATION_QUERY_GET); - SpanData grpcSpan = getGrpcSpanByName(SPAN_NAME_RUN_AGGREGATION_QUERY); + SpanData getSpan = getSpanByName(METHOD_NAME_AGGREGATION_QUERY_GET); + SpanData grpcSpan = getGrpcSpanByName(METHOD_NAME_RUN_AGGREGATION_QUERY); assertNotNull(getSpan); assertNotNull(grpcSpan); assertEquals(grpcSpan.getParentSpanId(), getSpan.getSpanId()); @@ -394,7 +395,7 @@ public void bulkWriterCommit() throws Exception { List spans = prepareSpans(); assertEquals(2, spans.size()); - assertSpanHierarchy(SPAN_NAME_BULK_WRITER_COMMIT, grpcSpanName(BATCH_WRITE_RPC_NAME)); + assertSpanHierarchy(METHOD_NAME_BULK_WRITER_COMMIT, grpcSpanName(BATCH_WRITE_RPC_NAME)); } @Test @@ -404,7 +405,7 @@ public void partitionQuery() throws Exception { List spans = prepareSpans(); assertEquals(2, spans.size()); - assertSpanHierarchy(SPAN_NAME_PARTITION_QUERY, grpcSpanName(SPAN_NAME_PARTITION_QUERY)); + assertSpanHierarchy(METHOD_NAME_PARTITION_QUERY, grpcSpanName(METHOD_NAME_PARTITION_QUERY)); } @Test @@ -413,7 +414,7 @@ public void collectionListDocuments() throws Exception { List spans = prepareSpans(); assertEquals(2, spans.size()); - assertSpanHierarchy(SPAN_NAME_COL_REF_LIST_DOCUMENTS, grpcSpanName(LIST_DOCUMENTS_RPC_NAME)); + assertSpanHierarchy(METHOD_NAME_COL_REF_LIST_DOCUMENTS, grpcSpanName(LIST_DOCUMENTS_RPC_NAME)); } @Test @@ -423,7 +424,7 @@ public void docRefCreate() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_CREATE, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_CREATE, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -433,7 +434,7 @@ public void docRefCreate2() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_CREATE, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_CREATE, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -443,7 +444,7 @@ public void docRefSet() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_SET, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_SET, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -457,7 +458,7 @@ public void docRefSet2() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_SET, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_SET, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -467,7 +468,7 @@ public void docRefSet3() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_SET, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_SET, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -477,7 +478,7 @@ public void docRefSet4() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_SET, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_SET, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -491,7 +492,7 @@ public void docRefUpdate() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_UPDATE, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_UPDATE, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -505,7 +506,7 @@ public void docRefUpdate2() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_UPDATE, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_UPDATE, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -515,7 +516,7 @@ public void docRefUpdate3() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_UPDATE, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_UPDATE, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -529,7 +530,7 @@ public void docRefUpdate4() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_UPDATE, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_UPDATE, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -543,7 +544,7 @@ public void docRefUpdate5() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_UPDATE, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_UPDATE, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -557,7 +558,7 @@ public void docRefUpdate6() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_UPDATE, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_UPDATE, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -567,7 +568,7 @@ public void docRefDelete() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_DELETE, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_DELETE, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -577,7 +578,7 @@ public void docRefDelete2() throws Exception { List spans = prepareSpans(); assertEquals(3, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_DELETE, SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_DOC_REF_DELETE, METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); } @Test @@ -586,7 +587,7 @@ public void docRefGet() throws Exception { List spans = prepareSpans(); assertEquals(2, spans.size()); - assertSpanHierarchy(SPAN_NAME_DOC_REF_GET, grpcSpanName(BATCH_GET_DOCUMENTS_RPC_NAME)); + assertSpanHierarchy(METHOD_NAME_DOC_REF_GET, grpcSpanName(BATCH_GET_DOCUMENTS_RPC_NAME)); } @Test @@ -595,7 +596,7 @@ public void docRefGet2() throws Exception { List spans = prepareSpans(); assertEquals(2, spans.size()); - assertSpanHierarchy(SPAN_NAME_DOC_REF_GET, grpcSpanName(BATCH_GET_DOCUMENTS_RPC_NAME)); + assertSpanHierarchy(METHOD_NAME_DOC_REF_GET, grpcSpanName(BATCH_GET_DOCUMENTS_RPC_NAME)); } @Test @@ -605,7 +606,7 @@ public void docListCollections() throws Exception { List spans = prepareSpans(); assertEquals(2, spans.size()); assertSpanHierarchy( - SPAN_NAME_DOC_REF_LIST_COLLECTIONS, grpcSpanName(LIST_COLLECTIONS_RPC_NAME)); + METHOD_NAME_DOC_REF_LIST_COLLECTIONS, grpcSpanName(LIST_COLLECTIONS_RPC_NAME)); } @Test @@ -630,8 +631,8 @@ public void queryGet() throws Exception { firestore.collection("col").whereEqualTo("foo", "my_non_existent_value").get().get(); List spans = prepareSpans(); assertEquals(2, spans.size()); - assertSpanHierarchy(SPAN_NAME_QUERY_GET, grpcSpanName(RUN_QUERY_RPC_NAME)); - SpanData span = getSpanByName(SPAN_NAME_QUERY_GET); + assertSpanHierarchy(METHOD_NAME_QUERY_GET, grpcSpanName(RUN_QUERY_RPC_NAME)); + SpanData span = getSpanByName(METHOD_NAME_QUERY_GET); assertTrue( hasEvent( span, @@ -687,32 +688,33 @@ public void transaction() throws Exception { List spans = prepareSpans(); assertEquals(11, spans.size()); assertSpanHierarchy( - SPAN_NAME_TRANSACTION_RUN, - SPAN_NAME_TRANSACTION_BEGIN, + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_BEGIN, grpcSpanName(BEGIN_TRANSACTION_RPC_NAME)); assertSpanHierarchy( - SPAN_NAME_TRANSACTION_RUN, - SPAN_NAME_TRANSACTION_GET_QUERY, + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_GET_QUERY, grpcSpanName(RUN_QUERY_RPC_NAME)); assertSpanHierarchy( - SPAN_NAME_TRANSACTION_RUN, - SPAN_NAME_TRANSACTION_GET_AGGREGATION_QUERY, + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_GET_AGGREGATION_QUERY, grpcSpanName(RUN_AGGREGATION_QUERY_RPC_NAME)); assertSpanHierarchy( - SPAN_NAME_TRANSACTION_RUN, - SPAN_NAME_TRANSACTION_GET_DOCUMENTS, + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_GET_DOCUMENTS, grpcSpanName(BATCH_GET_DOCUMENTS_RPC_NAME)); assertSpanHierarchy( - SPAN_NAME_TRANSACTION_RUN, SPAN_NAME_TRANSACTION_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + METHOD_NAME_TRANSACTION_RUN, METHOD_NAME_TRANSACTION_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); - Attributes commitAttributes = getSpanByName(SPAN_NAME_TRANSACTION_COMMIT).getAttributes(); + Attributes commitAttributes = getSpanByName(METHOD_NAME_TRANSACTION_COMMIT).getAttributes(); assertEquals( 2L, commitAttributes .get(AttributeKey.longKey("gcp.firestore." + ATTRIBUTE_KEY_DOC_COUNT)) .longValue()); - Attributes runTransactionAttributes = getSpanByName(SPAN_NAME_TRANSACTION_RUN).getAttributes(); + Attributes runTransactionAttributes = + getSpanByName(METHOD_NAME_TRANSACTION_RUN).getAttributes(); assertEquals( 5L, runTransactionAttributes @@ -754,13 +756,15 @@ public void transactionRollback() throws Exception { List spans = prepareSpans(); assertEquals(5, spans.size()); assertSpanHierarchy( - SPAN_NAME_TRANSACTION_RUN, - SPAN_NAME_TRANSACTION_BEGIN, + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_BEGIN, grpcSpanName(BEGIN_TRANSACTION_RPC_NAME)); assertSpanHierarchy( - SPAN_NAME_TRANSACTION_RUN, SPAN_NAME_TRANSACTION_ROLLBACK, grpcSpanName(ROLLBACK_RPC_NAME)); + METHOD_NAME_TRANSACTION_RUN, + METHOD_NAME_TRANSACTION_ROLLBACK, + grpcSpanName(ROLLBACK_RPC_NAME)); - SpanData runTransactionSpanData = getSpanByName(SPAN_NAME_TRANSACTION_RUN); + SpanData runTransactionSpanData = getSpanByName(METHOD_NAME_TRANSACTION_RUN); assertEquals(StatusCode.ERROR, runTransactionSpanData.getStatus().getStatusCode()); assertEquals(1, runTransactionSpanData.getEvents().size()); assertEquals( @@ -797,16 +801,16 @@ public void writeBatch() throws Exception { List spans = prepareSpans(); assertEquals(2, spans.size()); - assertSpanHierarchy(SPAN_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); + assertSpanHierarchy(METHOD_NAME_BATCH_COMMIT, grpcSpanName(COMMIT_RPC_NAME)); assertEquals( false, - getSpanByName(SPAN_NAME_BATCH_COMMIT) + getSpanByName(METHOD_NAME_BATCH_COMMIT) .getAttributes() .get(AttributeKey.booleanKey("gcp.firestore." + ATTRIBUTE_KEY_IS_TRANSACTIONAL)) .booleanValue()); assertEquals( 3L, - getSpanByName(SPAN_NAME_BATCH_COMMIT) + getSpanByName(METHOD_NAME_BATCH_COMMIT) .getAttributes() .get(AttributeKey.longKey("gcp.firestore." + ATTRIBUTE_KEY_DOC_COUNT)) .longValue()); diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/telemetry/EnabledTraceUtilTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/telemetry/EnabledTraceUtilTest.java index f131c30180..22e835f199 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/telemetry/EnabledTraceUtilTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/telemetry/EnabledTraceUtilTest.java @@ -20,6 +20,7 @@ import com.google.cloud.firestore.FirestoreOpenTelemetryOptions; import com.google.cloud.firestore.FirestoreOptions; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.sdk.OpenTelemetrySdk; import org.junit.Before; import org.junit.Test; @@ -31,19 +32,12 @@ public void setUp() { GlobalOpenTelemetry.resetForTest(); } - FirestoreOptions.Builder getBaseOptions() { - return FirestoreOptions.newBuilder().setProjectId("test-project").setDatabaseId("(default)"); + EnabledTraceUtil defaultEnabledTraceUtil() { + return new EnabledTraceUtil(FirestoreOptions.getDefaultInstance()); } - FirestoreOptions getTracingEnabledOptions() { - return getBaseOptions() - .setOpenTelemetryOptions( - FirestoreOpenTelemetryOptions.newBuilder().setTracingEnabled(true).build()) - .build(); - } - - EnabledTraceUtil newEnabledTraceUtil() { - return new EnabledTraceUtil(getTracingEnabledOptions()); + FirestoreOptions.Builder getBaseOptions() { + return FirestoreOptions.newBuilder().setProjectId("test-project").setDatabaseId("(default)"); } @Test @@ -53,39 +47,79 @@ public void usesOpenTelemetryFromOptions() { getBaseOptions() .setOpenTelemetryOptions( FirestoreOpenTelemetryOptions.newBuilder() - .setTracingEnabled(true) .setOpenTelemetry(myOpenTelemetrySdk) .build()) .build(); EnabledTraceUtil traceUtil = new EnabledTraceUtil(firestoreOptions); - assertThat(traceUtil.getOpenTelemetry()).isEqualTo(myOpenTelemetrySdk); + assertThat(traceUtil.getOpenTelemetry()).isSameInstanceAs(myOpenTelemetrySdk); } @Test public void usesGlobalOpenTelemetryIfOpenTelemetryInstanceNotProvided() { OpenTelemetrySdk.builder().buildAndRegisterGlobal(); + EnabledTraceUtil traceUtil = defaultEnabledTraceUtil(); + assertThat(traceUtil.getOpenTelemetry()).isSameInstanceAs(GlobalOpenTelemetry.get()); + } + + @Test + public void usesOpenTelemetryFromOptionsEvenIfGlobalOpenTelemetryExists() { + // Register a GlobalOpenTelemetry. + OpenTelemetrySdk.builder().buildAndRegisterGlobal(); + + // Pass in a *different* OpenTelemetry instance to Firestore to use. + OpenTelemetrySdk myOpenTelemetrySdk = OpenTelemetrySdk.builder().build(); FirestoreOptions firestoreOptions = getBaseOptions() .setOpenTelemetryOptions( - FirestoreOpenTelemetryOptions.newBuilder().setTracingEnabled(true).build()) + FirestoreOpenTelemetryOptions.newBuilder() + .setOpenTelemetry(myOpenTelemetrySdk) + .build()) .build(); EnabledTraceUtil traceUtil = new EnabledTraceUtil(firestoreOptions); - assertThat(traceUtil.getOpenTelemetry()).isEqualTo(GlobalOpenTelemetry.get()); + + // Assert Firestore uses the custom one, not the global one. + assertThat(traceUtil.getOpenTelemetry()).isSameInstanceAs(myOpenTelemetrySdk); + assertThat(traceUtil.getOpenTelemetry()).isNotSameInstanceAs(GlobalOpenTelemetry.get()); } @Test - public void enabledTraceUtilProvidesChannelConfigurator() { - assertThat(newEnabledTraceUtil().getChannelConfigurator()).isNotNull(); + public void defaultOptionsDoesNotRegisterGrpcChannelConfigurator() { + EnabledTraceUtil traceUtil = defaultEnabledTraceUtil(); + assertThat(traceUtil.getOpenTelemetry().getTracerProvider()) + .isSameInstanceAs(TracerProvider.noop()); + assertThat(traceUtil.getChannelConfigurator()).isNull(); + } + + @Test + public void globalOpenTelemetryRegistersGrpcChannelConfigurator() { + OpenTelemetrySdk.builder().buildAndRegisterGlobal(); + EnabledTraceUtil traceUtil = defaultEnabledTraceUtil(); + assertThat(traceUtil.getChannelConfigurator()).isNotNull(); + } + + @Test + public void openTelemetryInstanceRegistersGrpcChannelConfigurator() { + OpenTelemetrySdk myOpenTelemetrySdk = OpenTelemetrySdk.builder().build(); + FirestoreOptions firestoreOptions = + getBaseOptions() + .setOpenTelemetryOptions( + FirestoreOpenTelemetryOptions.newBuilder() + .setOpenTelemetry(myOpenTelemetrySdk) + .build()) + .build(); + EnabledTraceUtil traceUtil = new EnabledTraceUtil(firestoreOptions); + assertThat(traceUtil.getChannelConfigurator()).isNotNull(); } @Test public void usesEnabledContext() { - assertThat(newEnabledTraceUtil().currentContext() instanceof EnabledTraceUtil.Context).isTrue(); + assertThat(defaultEnabledTraceUtil().currentContext() instanceof EnabledTraceUtil.Context) + .isTrue(); } @Test public void usesEnabledSpan() { - EnabledTraceUtil traceUtil = newEnabledTraceUtil(); + EnabledTraceUtil traceUtil = defaultEnabledTraceUtil(); assertThat(traceUtil.currentSpan() instanceof EnabledTraceUtil.Span).isTrue(); assertThat(traceUtil.startSpan("foo") instanceof EnabledTraceUtil.Span).isTrue(); assertThat( @@ -95,14 +129,14 @@ public void usesEnabledSpan() { @Test public void usesEnabledScope() { - EnabledTraceUtil traceUtil = newEnabledTraceUtil(); + EnabledTraceUtil traceUtil = defaultEnabledTraceUtil(); assertThat(traceUtil.currentContext().makeCurrent() instanceof EnabledTraceUtil.Scope).isTrue(); assertThat(traceUtil.currentSpan().makeCurrent() instanceof EnabledTraceUtil.Scope).isTrue(); } @Test public void durationString() { - EnabledTraceUtil traceUtil = newEnabledTraceUtil(); + EnabledTraceUtil traceUtil = defaultEnabledTraceUtil(); Duration duration = Duration.ofSeconds(2, 9); assertThat(traceUtil.durationString(duration)).isEqualTo("2.000000009s"); diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/telemetry/TraceUtilTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/telemetry/TraceUtilTest.java index 5bb3be668c..e2c20b1dfe 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/telemetry/TraceUtilTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/telemetry/TraceUtilTest.java @@ -17,45 +17,17 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.cloud.firestore.FirestoreOpenTelemetryOptions; import com.google.cloud.firestore.FirestoreOptions; +import io.opentelemetry.api.trace.TracerProvider; import org.junit.Test; public class TraceUtilTest { @Test - public void defaultOptionsUseDisabledTraceUtil() { - TraceUtil traceUtil = - TraceUtil.getInstance( - FirestoreOptions.newBuilder() - .setProjectId("test-project") - .setDatabaseId("(default)") - .build()); - assertThat(traceUtil instanceof DisabledTraceUtil).isTrue(); - } - - @Test - public void tracingDisabledOptionsUseDisabledTraceUtil() { - TraceUtil traceUtil = - TraceUtil.getInstance( - FirestoreOptions.newBuilder() - .setProjectId("test-project") - .setDatabaseId("(default)") - .setOpenTelemetryOptions( - FirestoreOpenTelemetryOptions.newBuilder().setTracingEnabled(false).build()) - .build()); - assertThat(traceUtil instanceof DisabledTraceUtil).isTrue(); - } - - @Test - public void tracingEnabledOptionsUseEnabledTraceUtil() { - TraceUtil traceUtil = - TraceUtil.getInstance( - FirestoreOptions.newBuilder() - .setProjectId("test-project") - .setDatabaseId("(default)") - .setOpenTelemetryOptions( - FirestoreOpenTelemetryOptions.newBuilder().setTracingEnabled(true).build()) - .build()); + public void defaultOptionsUseNoopTracer() { + TraceUtil traceUtil = TraceUtil.getInstance(FirestoreOptions.getDefaultInstance()); assertThat(traceUtil instanceof EnabledTraceUtil).isTrue(); + EnabledTraceUtil enabledTraceUtil = (EnabledTraceUtil) traceUtil; + assertThat( + enabledTraceUtil.getOpenTelemetry().getTracerProvider().equals(TracerProvider.noop())); } } diff --git a/grpc-google-cloud-firestore-admin-v1/pom.xml b/grpc-google-cloud-firestore-admin-v1/pom.xml index 175a93eb11..4d394960ba 100644 --- a/grpc-google-cloud-firestore-admin-v1/pom.xml +++ b/grpc-google-cloud-firestore-admin-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-firestore-admin-v1 - 3.28.0 + 3.29.0 grpc-google-cloud-firestore-admin-v1 GRPC library for grpc-google-cloud-firestore-admin-v1 com.google.cloud google-cloud-firestore-parent - 3.28.0 + 3.29.0 diff --git a/grpc-google-cloud-firestore-v1/pom.xml b/grpc-google-cloud-firestore-v1/pom.xml index 9f155fdfa3..4ae9fee30c 100644 --- a/grpc-google-cloud-firestore-v1/pom.xml +++ b/grpc-google-cloud-firestore-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-firestore-v1 - 3.28.0 + 3.29.0 grpc-google-cloud-firestore-v1 GRPC library for grpc-google-cloud-firestore-v1 com.google.cloud google-cloud-firestore-parent - 3.28.0 + 3.29.0 diff --git a/pom.xml b/pom.xml index 6adc4d7cf4..26e165f1b8 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-firestore-parent pom - 3.28.0 + 3.29.0 Google Cloud Firestore Parent https://github.com/googleapis/java-firestore @@ -150,32 +150,32 @@ com.google.api.grpc proto-google-cloud-firestore-admin-v1 - 3.28.0 + 3.29.0 com.google.cloud google-cloud-firestore - 3.28.0 + 3.29.0 com.google.cloud proto-google-cloud-firestore-bundle-v1 - 3.28.0 + 3.29.0 com.google.api.grpc proto-google-cloud-firestore-v1 - 3.28.0 + 3.29.0 com.google.api.grpc grpc-google-cloud-firestore-admin-v1 - 3.28.0 + 3.29.0 com.google.api.grpc grpc-google-cloud-firestore-v1 - 3.28.0 + 3.29.0 diff --git a/proto-google-cloud-firestore-admin-v1/pom.xml b/proto-google-cloud-firestore-admin-v1/pom.xml index 243fca5150..fdcb0c7539 100644 --- a/proto-google-cloud-firestore-admin-v1/pom.xml +++ b/proto-google-cloud-firestore-admin-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-firestore-admin-v1 - 3.28.0 + 3.29.0 proto-google-cloud-firestore-admin-v1 PROTO library for proto-google-cloud-firestore-admin-v1 com.google.cloud google-cloud-firestore-parent - 3.28.0 + 3.29.0 diff --git a/proto-google-cloud-firestore-bundle-v1/pom.xml b/proto-google-cloud-firestore-bundle-v1/pom.xml index 86717eb901..97ac81c257 100644 --- a/proto-google-cloud-firestore-bundle-v1/pom.xml +++ b/proto-google-cloud-firestore-bundle-v1/pom.xml @@ -5,14 +5,14 @@ 4.0.0 proto-google-cloud-firestore-bundle-v1 - 3.28.0 + 3.29.0 proto-google-cloud-firestore-bundle-v1 PROTO library for proto-google-cloud-firestore-bundle-v1 com.google.cloud google-cloud-firestore-parent - 3.28.0 + 3.29.0 diff --git a/proto-google-cloud-firestore-v1/pom.xml b/proto-google-cloud-firestore-v1/pom.xml index 30fc436edc..bbee34f761 100644 --- a/proto-google-cloud-firestore-v1/pom.xml +++ b/proto-google-cloud-firestore-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-firestore-v1 - 3.28.0 + 3.29.0 proto-google-cloud-firestore-v1 PROTO library for proto-google-cloud-firestore-v1 com.google.cloud google-cloud-firestore-parent - 3.28.0 + 3.29.0 diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index edb5eeee4e..d5af9e33a0 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -30,7 +30,7 @@ com.google.cloud google-cloud-firestore - 3.27.4 + 3.28.0 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 0cc6325ddf..a565c68e33 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -29,7 +29,7 @@ com.google.cloud google-cloud-firestore - 3.27.4 + 3.28.0 diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index deb578101a..53fa89e02f 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -34,7 +34,7 @@ com.google.cloud libraries-bom - 26.49.0 + 26.50.0 pom import diff --git a/versions.txt b/versions.txt index d23a72b429..2460651983 100644 --- a/versions.txt +++ b/versions.txt @@ -1,11 +1,11 @@ # Format: # module:released-version:current-version -google-cloud-firestore:3.28.0:3.28.0 -google-cloud-firestore-admin:3.28.0:3.28.0 -google-cloud-firestore-bom:3.28.0:3.28.0 -grpc-google-cloud-firestore-admin-v1:3.28.0:3.28.0 -grpc-google-cloud-firestore-v1:3.28.0:3.28.0 -proto-google-cloud-firestore-admin-v1:3.28.0:3.28.0 -proto-google-cloud-firestore-v1:3.28.0:3.28.0 -proto-google-cloud-firestore-bundle-v1:3.28.0:3.28.0 +google-cloud-firestore:3.29.0:3.29.0 +google-cloud-firestore-admin:3.29.0:3.29.0 +google-cloud-firestore-bom:3.29.0:3.29.0 +grpc-google-cloud-firestore-admin-v1:3.29.0:3.29.0 +grpc-google-cloud-firestore-v1:3.29.0:3.29.0 +proto-google-cloud-firestore-admin-v1:3.29.0:3.29.0 +proto-google-cloud-firestore-v1:3.29.0:3.29.0 +proto-google-cloud-firestore-bundle-v1:3.29.0:3.29.0