modifyFamiliesAsync(ModifyColumnFamiliesRequest request)
* client.deleteTable("my-table");
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public void deleteTable(String tableId) {
ApiExceptions.callAndTranslateApiException(deleteTableAsync(tableId));
}
/**
- * Asynchronously deletes the table specified by the table ID.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#deleteTableCallable()}.
+ *
+ * Asynchronously deletes the table specified by the table ID.
*
*
Sample code:
*
@@ -476,6 +536,7 @@ public void deleteTable(String tableId) {
* }
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture deleteTableAsync(String tableId) {
DeleteTableRequest request =
DeleteTableRequest.newBuilder().setName(getTableName(tableId)).build();
@@ -484,7 +545,10 @@ public ApiFuture deleteTableAsync(String tableId) {
}
/**
- * Checks if the table specified by the table ID exists.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getTable(com.google.bigtable.admin.v2.GetTableRequest)}.
+ *
+ * Checks if the table specified by the table ID exists.
*
*
Sample code:
*
@@ -494,12 +558,16 @@ public ApiFuture deleteTableAsync(String tableId) {
* }
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public boolean exists(String tableId) {
return ApiExceptions.callAndTranslateApiException(existsAsync(tableId));
}
/**
- * Asynchronously checks if the table specified by the table ID exists.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getTableCallable()}.
+ *
+ * Asynchronously checks if the table specified by the table ID exists.
*
*
Sample code:
*
@@ -525,6 +593,7 @@ public boolean exists(String tableId) {
* );
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture existsAsync(String tableId) {
ApiFuture protoFuture =
@@ -554,7 +623,10 @@ public Boolean apply(NotFoundException ignored) {
}
/**
- * Gets the table metadata by table ID.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getTable(com.google.bigtable.admin.v2.GetTableRequest)}.
+ *
+ * Gets the table metadata by table ID.
*
*
Sample code:
*
@@ -569,12 +641,16 @@ public Boolean apply(NotFoundException ignored) {
* }
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public Table getTable(String tableId) {
return ApiExceptions.callAndTranslateApiException(getTableAsync(tableId));
}
/**
- * Asynchronously gets the table metadata by table ID.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getTableCallable()}.
+ *
+ *
Asynchronously gets the table metadata by table ID.
*
*
Sample code:
*
@@ -601,6 +677,7 @@ public Table getTable(String tableId) {
* }
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture
getTableAsync(String tableId) {
return getTableAsync(tableId, com.google.bigtable.admin.v2.Table.View.SCHEMA_VIEW);
}
@@ -614,21 +691,29 @@ private ApiFuture getTableAsync(
}
/**
- * Gets the current encryption info for the table across all of the clusters.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getTable(com.google.bigtable.admin.v2.GetTableRequest)}.
+ *
+ * Gets the current encryption info for the table across all of the clusters.
*
*
The returned Map will be keyed by cluster id and contain a status for all of the keys in
* use.
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public Map> getEncryptionInfo(String tableId) {
return ApiExceptions.callAndTranslateApiException(getEncryptionInfoAsync(tableId));
}
/**
- * Asynchronously gets the current encryption info for the table across all of the clusters.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getTableCallable()}.
+ *
+ * Asynchronously gets the current encryption info for the table across all of the clusters.
*
*
The returned Map will be keyed by cluster id and contain a status for all of the keys in
* use.
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture>> getEncryptionInfoAsync(String tableId) {
GetTableRequest request =
GetTableRequest.newBuilder()
@@ -660,7 +745,10 @@ public Map> apply(com.google.bigtable.admin.v2.Tabl
}
/**
- * Lists all table IDs in the instance.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#listTables(com.google.bigtable.admin.v2.ListTablesRequest)}.
+ *
+ * Lists all table IDs in the instance.
*
*
Sample code:
*
@@ -672,12 +760,16 @@ public Map> apply(com.google.bigtable.admin.v2.Tabl
* }
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public List listTables() {
return ApiExceptions.callAndTranslateApiException(listTablesAsync());
}
/**
- * Asynchronously lists all table IDs in the instance.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#listTablesPagedCallable()}.
+ *
+ * Asynchronously lists all table IDs in the instance.
*
*
Sample code:
*
@@ -702,6 +794,7 @@ public List listTables() {
* );
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture> listTablesAsync() {
ListTablesRequest request =
ListTablesRequest.newBuilder()
@@ -765,7 +858,10 @@ public List apply(List protos) {
}
/**
- * Drops rows by the specified row key prefix and table ID.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#dropRowRange(com.google.bigtable.admin.v2.DropRowRangeRequest)}.
+ *
+ * Drops rows by the specified row key prefix and table ID.
*
*
Please note that this method is considered part of the admin API and is rate limited.
*
@@ -775,12 +871,16 @@ public List apply(List protos) {
* client.dropRowRange("my-table", "prefix");
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public void dropRowRange(String tableId, String rowKeyPrefix) {
ApiExceptions.callAndTranslateApiException(dropRowRangeAsync(tableId, rowKeyPrefix));
}
/**
- * Asynchronously drops rows by the specified row key prefix and table ID.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#dropRowRangeCallable()}.
+ *
+ * Asynchronously drops rows by the specified row key prefix and table ID.
*
*
Please note that this method is considered part of the admin API and is rate limited.
*
@@ -805,12 +905,16 @@ public void dropRowRange(String tableId, String rowKeyPrefix) {
* }
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture dropRowRangeAsync(String tableId, String rowKeyPrefix) {
return dropRowRangeAsync(tableId, ByteString.copyFromUtf8(rowKeyPrefix));
}
/**
- * Drops rows by the specified row key prefix and table ID.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#dropRowRange(com.google.bigtable.admin.v2.DropRowRangeRequest)}.
+ *
+ * Drops rows by the specified row key prefix and table ID.
*
*
Please note that this method is considered part of the admin API and is rate limited.
*
@@ -821,12 +925,16 @@ public ApiFuture dropRowRangeAsync(String tableId, String rowKeyPrefix) {
* }
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public void dropRowRange(String tableId, ByteString rowKeyPrefix) {
ApiExceptions.callAndTranslateApiException(dropRowRangeAsync(tableId, rowKeyPrefix));
}
/**
- * Asynchronously drops rows by the specified row key prefix and table ID.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#dropRowRangeCallable()}.
+ *
+ * Asynchronously drops rows by the specified row key prefix and table ID.
*
*
Please note that this method is considered part of the admin API and is rate limited.
*
@@ -851,6 +959,7 @@ public void dropRowRange(String tableId, ByteString rowKeyPrefix) {
* }
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture dropRowRangeAsync(String tableId, ByteString rowKeyPrefix) {
DropRowRangeRequest request =
DropRowRangeRequest.newBuilder()
@@ -862,7 +971,10 @@ public ApiFuture dropRowRangeAsync(String tableId, ByteString rowKeyPrefix
}
/**
- * Drops all data in the table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#dropRowRange(com.google.bigtable.admin.v2.DropRowRangeRequest)}.
+ *
+ * Drops all data in the table.
*
*
Sample code:
*
@@ -870,12 +982,16 @@ public ApiFuture dropRowRangeAsync(String tableId, ByteString rowKeyPrefix
* client.dropAllRows("my-table");
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public void dropAllRows(String tableId) {
ApiExceptions.callAndTranslateApiException(dropAllRowsAsync(tableId));
}
/**
- * Asynchronously drops all data in the table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#dropRowRangeCallable()}.
+ *
+ * Asynchronously drops all data in the table.
*
*
Sample code:
*
@@ -898,6 +1014,7 @@ public void dropAllRows(String tableId) {
* }
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture dropAllRowsAsync(String tableId) {
DropRowRangeRequest request =
DropRowRangeRequest.newBuilder()
@@ -909,7 +1026,10 @@ public ApiFuture dropAllRowsAsync(String tableId) {
}
/**
- * Blocks the current thread until replication has caught up to the point when this method was
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#checkConsistency(com.google.bigtable.admin.v2.CheckConsistencyRequest)}.
+ *
+ * Blocks the current thread until replication has caught up to the point when this method was
* called. This allows callers to make sure that their mutations have been replicated across all
* of their clusters.
*
@@ -921,6 +1041,7 @@ public ApiFuture dropAllRowsAsync(String tableId) {
*
* @throws com.google.api.gax.retrying.PollException when polling exceeds the total timeout
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public void awaitReplication(String tableId) {
// TODO(igorbernstein2): remove usage of typesafe names
com.google.bigtable.admin.v2.TableName tableName =
@@ -936,7 +1057,10 @@ public void awaitConsistency(ConsistencyRequest consistencyRequest) {
}
/**
- * Creates a backup with the specified configuration.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#createBackup(com.google.bigtable.admin.v2.CreateBackupRequest)}.
+ *
+ * Creates a backup with the specified configuration.
*
*
Sample code
*
@@ -948,12 +1072,16 @@ public void awaitConsistency(ConsistencyRequest consistencyRequest) {
* Backup response = client.createBackup(request);
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public Backup createBackup(CreateBackupRequest request) {
return ApiExceptions.callAndTranslateApiException(createBackupAsync(request));
}
/**
- * Creates a backup with the specified configuration asynchronously.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#createBackupOperationCallable()}.
+ *
+ *
Creates a backup with the specified configuration asynchronously.
*
*
Sample code
*
@@ -979,6 +1107,7 @@ public Backup createBackup(CreateBackupRequest request) {
* );
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture createBackupAsync(CreateBackupRequest request) {
return ApiFutures.transform(
stub.createBackupOperationCallable().futureCall(request.toProto(projectId, instanceId)),
@@ -992,7 +1121,10 @@ public Backup apply(com.google.bigtable.admin.v2.Backup backupProto) {
}
/**
- * Gets a backup with the specified backup ID in the specified cluster.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getBackup(com.google.bigtable.admin.v2.GetBackupRequest)}.
+ *
+ * Gets a backup with the specified backup ID in the specified cluster.
*
*
Sample code
*
@@ -1000,12 +1132,16 @@ public Backup apply(com.google.bigtable.admin.v2.Backup backupProto) {
* Backup backup = client.getBackup(clusterId, backupId);
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public Backup getBackup(String clusterId, String backupId) {
return ApiExceptions.callAndTranslateApiException(getBackupAsync(clusterId, backupId));
}
/**
- * Gets a backup with the specified backup ID in the specified cluster asynchronously.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getBackupCallable()}.
+ *
+ *
Gets a backup with the specified backup ID in the specified cluster asynchronously.
*
*
Sample code
*
@@ -1027,6 +1163,7 @@ public Backup getBackup(String clusterId, String backupId) {
* );
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture getBackupAsync(String clusterId, String backupId) {
GetBackupRequest request =
GetBackupRequest.newBuilder()
@@ -1044,7 +1181,10 @@ public Backup apply(com.google.bigtable.admin.v2.Backup backup) {
}
/**
- * Lists backups in the specified cluster.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#listBackups(com.google.bigtable.admin.v2.ListBackupsRequest)}.
+ *
+ * Lists backups in the specified cluster.
*
*
Sample code
*
@@ -1052,12 +1192,16 @@ public Backup apply(com.google.bigtable.admin.v2.Backup backup) {
* List backups = client.listBackups(clusterId);
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public List listBackups(String clusterId) {
return ApiExceptions.callAndTranslateApiException(listBackupsAsync(clusterId));
}
/**
- * Lists backups in the specified cluster asynchronously.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#listBackupsPagedCallable()}.
+ *
+ * Lists backups in the specified cluster asynchronously.
*
*
Sample code:
*
@@ -1082,6 +1226,7 @@ public List listBackups(String clusterId) {
* );
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture> listBackupsAsync(String clusterId) {
ListBackupsRequest request =
ListBackupsRequest.newBuilder()
@@ -1145,7 +1290,10 @@ public List apply(List protos) {
}
/**
- * Deletes a backup with the specified backup ID in the specified cluster.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#deleteBackup(com.google.bigtable.admin.v2.DeleteBackupRequest)}.
+ *
+ * Deletes a backup with the specified backup ID in the specified cluster.
*
*
Sample code
*
@@ -1153,12 +1301,16 @@ public List apply(List protos) {
* client.deleteBackup(clusterId, backupId);
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public void deleteBackup(String clusterId, String backupId) {
ApiExceptions.callAndTranslateApiException(deleteBackupAsync(clusterId, backupId));
}
/**
- * Deletes a backup with the specified backup ID in the specified cluster asynchronously.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#deleteBackupCallable()}.
+ *
+ * Deletes a backup with the specified backup ID in the specified cluster asynchronously.
*
*
Sample code
*
@@ -1180,6 +1332,7 @@ public void deleteBackup(String clusterId, String backupId) {
* );
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture deleteBackupAsync(String clusterId, String backupId) {
DeleteBackupRequest request =
DeleteBackupRequest.newBuilder()
@@ -1190,7 +1343,10 @@ public ApiFuture deleteBackupAsync(String clusterId, String backupId) {
}
/**
- * Updates a backup with the specified configuration.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#updateBackup(com.google.bigtable.admin.v2.UpdateBackupRequest)}.
+ *
+ * Updates a backup with the specified configuration.
*
*
Sample code
*
@@ -1198,12 +1354,16 @@ public ApiFuture deleteBackupAsync(String clusterId, String backupId) {
* Backup backup = client.updateBackup(clusterId, backupId);
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public Backup updateBackup(UpdateBackupRequest request) {
return ApiExceptions.callAndTranslateApiException(updateBackupAsync(request));
}
/**
- * Updates a backup with the specified configuration asynchronously.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#updateBackupCallable()}.
+ *
+ * Updates a backup with the specified configuration asynchronously.
*
*
Sample code
*
@@ -1225,6 +1385,7 @@ public Backup updateBackup(UpdateBackupRequest request) {
* );
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture updateBackupAsync(UpdateBackupRequest request) {
return ApiFutures.transform(
stub.updateBackupCallable().futureCall(request.toProto(projectId, instanceId)),
@@ -1238,7 +1399,10 @@ public Backup apply(com.google.bigtable.admin.v2.Backup proto) {
}
/**
- * Restores a backup to a new table with the specified configuration.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#restoreTable(com.google.bigtable.admin.v2.RestoreTableRequest)}.
+ *
+ * Restores a backup to a new table with the specified configuration.
*
*
Sample code
*
@@ -1247,12 +1411,18 @@ public Backup apply(com.google.bigtable.admin.v2.Backup proto) {
* client.restoreTable(RestoreTableRequest.of(clusterId, backupId).setTableId(tableId));
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public RestoredTableResult restoreTable(RestoreTableRequest request)
throws ExecutionException, InterruptedException {
return ApiExceptions.callAndTranslateApiException(restoreTableAsync(request));
}
- /** Restores a backup to a new table with the specified configuration asynchronously.
+ /**
+ *
This method is obsolete. For the recommended proto-based approach, please see
+ * {@link com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#restoreTableAsync(com.google.bigtable.admin.v2.RestoreTableRequest)}.
+ *
+ *
Restores a backup to a new table with the specified configuration asynchronously.
+ *
*
Sample code
*
*
{@code
@@ -1273,7 +1443,8 @@ public RestoredTableResult restoreTable(RestoreTableRequest request)
* MoreExecutors.directExecutor()
* );
*
- * */
+ */
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture restoreTableAsync(RestoreTableRequest request) {
final OperationFuture future =
this.stub
@@ -1296,6 +1467,37 @@ public ApiFuture apply(com.google.bigtable.admin.v2.Table t
MoreExecutors.directExecutor());
}
+ /**
+ * Awaits the completion of the "Optimize Restored Table" operation.
+ *
+ * This method blocks until the restore operation is complete, extracts the optimization token,
+ * and returns an ApiFuture for the optimization phase.
+ *
+ * @param restoreFuture The future returned by restoreTableAsync().
+ * @return An ApiFuture that tracks the optimization progress.
+ */
+ public ApiFuture awaitOptimizeRestoredTable(ApiFuture restoreFuture) {
+ // 1. Block and wait for the restore operation to complete
+ RestoredTableResult result;
+ try {
+ result = restoreFuture.get();
+ } catch (Exception e) {
+ throw new RuntimeException("Restore operation failed", e);
+ }
+
+ // 2. Extract the operation token from the result
+ // (RestoredTableResult already wraps the OptimizeRestoredTableOperationToken)
+ OptimizeRestoredTableOperationToken token = result.getOptimizeRestoredTableOperationToken();
+
+ if (token == null || Strings.isNullOrEmpty(token.getOperationName())) {
+ // If there is no optimization operation, return immediate success.
+ return ApiFutures.immediateFuture(Empty.getDefaultInstance());
+ }
+
+ // 3. Return the future for the optimization operation
+ return stub.awaitOptimizeRestoredTableCallable().resumeFutureCall(token.getOperationName());
+ }
+
/**
* Awaits a restored table is fully optimized.
*
@@ -1345,7 +1547,10 @@ public ApiFuture awaitOptimizeRestoredTableAsync(
}
/**
- * Copy an existing backup to a new backup in a Cloud Bigtable cluster with the specified
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#copyBackup(com.google.bigtable.admin.v2.CopyBackupRequest)}.
+ *
+ * Copy an existing backup to a new backup in a Cloud Bigtable cluster with the specified
* configuration.
*
*
Sample code Note: You want to create the client with project and instance where you want the
@@ -1382,12 +1587,16 @@ public ApiFuture awaitOptimizeRestoredTableAsync(
* Backup response = client.copyBackup(request);
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public Backup copyBackup(CopyBackupRequest request) {
return ApiExceptions.callAndTranslateApiException(copyBackupAsync(request));
}
/**
- * Creates a copy of a backup from an existing backup in a Cloud Bigtable cluster with the
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#copyBackupOperationCallable()}.
+ *
+ * Creates a copy of a backup from an existing backup in a Cloud Bigtable cluster with the
* specified configuration asynchronously.
*
*
Sample code
@@ -1414,6 +1623,7 @@ public Backup copyBackup(CopyBackupRequest request) {
* );
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture copyBackupAsync(CopyBackupRequest request) {
return ApiFutures.transform(
stub.copyBackupOperationCallable().futureCall(request.toProto(projectId, instanceId)),
@@ -1427,9 +1637,12 @@ public Backup apply(com.google.bigtable.admin.v2.Backup backupProto) {
}
/**
- * Returns a future that is resolved when replication has caught up to the point when this method
- * was called. This allows callers to make sure that their mutations have been replicated across
- * all of their clusters.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#generateConsistencyTokenCallable()}.
+ *
+ * Returns a future that is resolved when replication has caught up to the point when this
+ * method was called. This allows callers to make sure that their mutations have been replicated
+ * across all of their clusters.
*
*
Sample code:
*
@@ -1453,6 +1666,7 @@ public Backup apply(com.google.bigtable.admin.v2.Backup backupProto) {
* }
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture awaitReplicationAsync(final String tableId) {
// TODO(igorbernstein2): remove usage of typesafe names
com.google.bigtable.admin.v2.TableName tableName =
@@ -1485,7 +1699,10 @@ public ApiFuture waitForConsistencyAsync(String tableId, String consistenc
}
/**
- * Creates a new authorized view with the specified configuration.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#createAuthorizedView(com.google.bigtable.admin.v2.CreateAuthorizedViewRequest)}.
+ *
+ * Creates a new authorized view with the specified configuration.
*
*
Sample code:
*
@@ -1504,12 +1721,16 @@ public ApiFuture waitForConsistencyAsync(String tableId, String consistenc
*
* @see CreateAuthorizedViewRequest for available options.
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public AuthorizedView createAuthorizedView(CreateAuthorizedViewRequest request) {
return ApiExceptions.callAndTranslateApiException(createAuthorizedViewAsync(request));
}
/**
- * Asynchronously creates a new authorized view with the specified configuration.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#createAuthorizedViewOperationCallable()}.
+ *
+ * Asynchronously creates a new authorized view with the specified configuration.
*
*
Sample code:
*
@@ -1542,6 +1763,7 @@ public AuthorizedView createAuthorizedView(CreateAuthorizedViewRequest request)
*
* @see CreateAuthorizedViewRequest for available options.
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture createAuthorizedViewAsync(CreateAuthorizedViewRequest request) {
return ApiFutures.transform(
stub.createAuthorizedViewOperationCallable()
@@ -1557,7 +1779,10 @@ public AuthorizedView apply(
}
/**
- * Updates an existing authorized view with the specified configuration.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#updateAuthorizedView(com.google.bigtable.admin.v2.UpdateAuthorizedViewRequest)}.
+ *
+ * Updates an existing authorized view with the specified configuration.
*
*
Sample code:
*
@@ -1572,12 +1797,16 @@ public AuthorizedView apply(
*
* @see UpdateAuthorizedViewRequest for available options.
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public AuthorizedView updateAuthorizedView(UpdateAuthorizedViewRequest request) {
return ApiExceptions.callAndTranslateApiException(updateAuthorizedViewAsync(request));
}
/**
- * Asynchronously updates an existing authorized view with the specified configuration.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#updateAuthorizedViewOperationCallable()}.
+ *
+ *
Asynchronously updates an existing authorized view with the specified configuration.
*
*
Sample code:
*
@@ -1606,6 +1835,7 @@ public AuthorizedView updateAuthorizedView(UpdateAuthorizedViewRequest request)
*
* @see UpdateAuthorizedViewRequest for available options.
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture updateAuthorizedViewAsync(UpdateAuthorizedViewRequest request) {
return ApiFutures.transform(
stub.updateAuthorizedViewOperationCallable()
@@ -1621,7 +1851,10 @@ public AuthorizedView apply(
}
/**
- * Gets an authorized view with the specified authorized view ID in the specified table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getAuthorizedView(com.google.bigtable.admin.v2.GetAuthorizedViewRequest)}.
+ *
+ * Gets an authorized view with the specified authorized view ID in the specified table.
*
*
Sample code:
*
@@ -1629,14 +1862,18 @@ public AuthorizedView apply(
* AuthorizedView authorizedView = client.getAuthorizedView("my-table", "my-authorized-view");
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public AuthorizedView getAuthorizedView(String tableId, String authorizedViewId) {
return ApiExceptions.callAndTranslateApiException(
getAuthorizedViewAsync(tableId, authorizedViewId));
}
/**
- * Asynchronously gets an authorized view with the specified authorized view ID in the specified
- * table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getAuthorizedViewCallable()}.
+ *
+ *
Asynchronously gets an authorized view with the specified authorized view ID in the
+ * specified table.
*
*
Sample code:
*
@@ -1658,6 +1895,7 @@ public AuthorizedView getAuthorizedView(String tableId, String authorizedViewId)
* );
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture getAuthorizedViewAsync(String tableId, String authorizedViewId) {
GetAuthorizedViewRequest request =
GetAuthorizedViewRequest.newBuilder()
@@ -1677,7 +1915,10 @@ public AuthorizedView apply(
}
/**
- * Lists all authorized view IDs in the specified table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#listAuthorizedViews(com.google.bigtable.admin.v2.ListAuthorizedViewsRequest)}.
+ *
+ * Lists all authorized view IDs in the specified table.
*
*
Sample code:
*
@@ -1685,12 +1926,16 @@ public AuthorizedView apply(
* List authorizedViews = client.listAuthorizedViews("my-table");
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public List listAuthorizedViews(String tableId) {
return ApiExceptions.callAndTranslateApiException(listAuthorizedViewsAsync(tableId));
}
/**
- * Asynchronously lists all authorized view IDs in the specified table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#listAuthorizedViewsPagedCallable()}.
+ *
+ * Asynchronously lists all authorized view IDs in the specified table.
*
*
Sample code:
*
@@ -1715,6 +1960,7 @@ public List listAuthorizedViews(String tableId) {
* );
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture> listAuthorizedViewsAsync(String tableId) {
ListAuthorizedViewsRequest request =
ListAuthorizedViewsRequest.newBuilder()
@@ -1780,9 +2026,12 @@ public List apply(List prot
}
/**
- * Deletes an authorized view with the specified authorized view ID in the specified table. Note
- * that the deletion is prohibited if the authorized view has deletion_protection field set to
- * true.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#deleteAuthorizedView(com.google.bigtable.admin.v2.DeleteAuthorizedViewRequest)}.
+ *
+ * Deletes an authorized view with the specified authorized view ID in the specified table.
+ * Note that the deletion is prohibited if the authorized view has deletion_protection field set
+ * to true.
*
*
Sample code:
*
@@ -1790,13 +2039,17 @@ public List apply(List prot
* client.deleteAuthorizedView("my-table", "my-authorized-view");
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public void deleteAuthorizedView(String tableId, String authorizedViewId) {
ApiExceptions.callAndTranslateApiException(
deleteAuthorizedViewAsync(tableId, authorizedViewId));
}
/**
- * Asynchronously deletes an authorized view with the specified authorized view ID in the
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#deleteAuthorizedViewCallable()}.
+ *
+ * Asynchronously deletes an authorized view with the specified authorized view ID in the
* specified table. Note that the deletion is prohibited if the authorized view has
* deletion_protection field set to true.
*
@@ -1820,6 +2073,7 @@ public void deleteAuthorizedView(String tableId, String authorizedViewId) {
* );
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture deleteAuthorizedViewAsync(String tableId, String authorizedViewId) {
DeleteAuthorizedViewRequest request =
DeleteAuthorizedViewRequest.newBuilder()
@@ -1831,7 +2085,10 @@ public ApiFuture deleteAuthorizedViewAsync(String tableId, String authoriz
}
/**
- * Creates a new schema bundle with the specified configuration.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#createSchemaBundle(com.google.bigtable.admin.v2.CreateSchemaBundleRequest)}.
+ *
+ * Creates a new schema bundle with the specified configuration.
*
*
Sample code:
*
@@ -1849,12 +2106,16 @@ public ApiFuture deleteAuthorizedViewAsync(String tableId, String authoriz
*
* @see CreateSchemaBundleRequest for available options.
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public SchemaBundle createSchemaBundle(CreateSchemaBundleRequest request) {
return ApiExceptions.callAndTranslateApiException(createSchemaBundleAsync(request));
}
/**
- * Asynchronously creates a new schema bundle with the specified configuration.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#createSchemaBundleOperationCallable()}.
+ *
+ * Asynchronously creates a new schema bundle with the specified configuration.
*
*
Sample code:
*
@@ -1885,6 +2146,7 @@ public SchemaBundle createSchemaBundle(CreateSchemaBundleRequest request) {
*
* @see CreateSchemaBundleRequest for available options.
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture createSchemaBundleAsync(CreateSchemaBundleRequest request) {
return ApiFutures.transform(
stub.createSchemaBundleOperationCallable()
@@ -1899,7 +2161,10 @@ public SchemaBundle apply(com.google.bigtable.admin.v2.SchemaBundle schemaBundle
}
/**
- * Updates an existing schema bundle with the specified configuration.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#updateSchemaBundle(com.google.bigtable.admin.v2.UpdateSchemaBundleRequest)}.
+ *
+ * Updates an existing schema bundle with the specified configuration.
*
*
Sample code:
*
@@ -1913,12 +2178,16 @@ public SchemaBundle apply(com.google.bigtable.admin.v2.SchemaBundle schemaBundle
*
* @see UpdateSchemaBundleRequest for available options.
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public SchemaBundle updateSchemaBundle(UpdateSchemaBundleRequest request) {
return ApiExceptions.callAndTranslateApiException(updateSchemaBundleAsync(request));
}
/**
- * Asynchronously updates an existing schema bundle with the specified configuration.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#updateSchemaBundleOperationCallable()}.
+ *
+ *
Asynchronously updates an existing schema bundle with the specified configuration.
*
*
Sample code:
*
@@ -1945,6 +2214,7 @@ public SchemaBundle updateSchemaBundle(UpdateSchemaBundleRequest request) {
*
* @see UpdateSchemaBundleRequest for available options.
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture updateSchemaBundleAsync(UpdateSchemaBundleRequest request) {
return ApiFutures.transform(
stub.updateSchemaBundleOperationCallable()
@@ -1959,7 +2229,10 @@ public SchemaBundle apply(com.google.bigtable.admin.v2.SchemaBundle schemaBundle
}
/**
- * Gets an schema bundle with the specified schema bundle ID in the specified table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getSchemaBundle(com.google.bigtable.admin.v2.GetSchemaBundleRequest)}.
+ *
+ * Gets an schema bundle with the specified schema bundle ID in the specified table.
*
*
Sample code:
*
@@ -1967,13 +2240,17 @@ public SchemaBundle apply(com.google.bigtable.admin.v2.SchemaBundle schemaBundle
* SchemaBundle schemaBundle = client.getSchemaBundle("my-table", "my-schema-bundle");
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public SchemaBundle getSchemaBundle(String tableId, String schemaBundleId) {
return ApiExceptions.callAndTranslateApiException(
getSchemaBundleAsync(tableId, schemaBundleId));
}
/**
- * Asynchronously gets an schema bundle with the specified schema bundle ID in the specified
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getSchemaBundleCallable()}.
+ *
+ *
Asynchronously gets an schema bundle with the specified schema bundle ID in the specified
* table.
*
*
Sample code:
@@ -1995,6 +2272,7 @@ public SchemaBundle getSchemaBundle(String tableId, String schemaBundleId) {
* MoreExecutors.directExecutor());
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture getSchemaBundleAsync(String tableId, String schemaBundleId) {
GetSchemaBundleRequest request =
GetSchemaBundleRequest.newBuilder()
@@ -2013,7 +2291,10 @@ public SchemaBundle apply(com.google.bigtable.admin.v2.SchemaBundle schemaBundle
}
/**
- * Lists all schema bundle IDs in the specified table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#listSchemaBundles(com.google.bigtable.admin.v2.ListSchemaBundlesRequest)}.
+ *
+ * Lists all schema bundle IDs in the specified table.
*
*
Sample code:
*
@@ -2021,12 +2302,16 @@ public SchemaBundle apply(com.google.bigtable.admin.v2.SchemaBundle schemaBundle
* List schemaBundles = client.listSchemaBundles("my-table");
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public List listSchemaBundles(String tableId) {
return ApiExceptions.callAndTranslateApiException(listSchemaBundlesAsync(tableId));
}
/**
- * Asynchronously lists all schema bundle IDs in the specified table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#listSchemaBundlesPagedCallable()}.
+ *
+ * Asynchronously lists all schema bundle IDs in the specified table.
*
*
Sample code:
*
@@ -2050,6 +2335,7 @@ public List listSchemaBundles(String tableId) {
* MoreExecutors.directExecutor());
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture> listSchemaBundlesAsync(String tableId) {
ListSchemaBundlesRequest request =
ListSchemaBundlesRequest.newBuilder()
@@ -2116,7 +2402,10 @@ public List apply(List protos
}
/**
- * Deletes an schema bundle with the specified schema bundle ID in the specified table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#deleteSchemaBundle(com.google.bigtable.admin.v2.DeleteSchemaBundleRequest)}.
+ *
+ * Deletes an schema bundle with the specified schema bundle ID in the specified table.
*
*
Sample code:
*
@@ -2124,12 +2413,16 @@ public List apply(List protos
* client.deleteSchemaBundle("my-table", "my-schema-bundle");
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public void deleteSchemaBundle(String tableId, String schemaBundleId) {
ApiExceptions.callAndTranslateApiException(deleteSchemaBundleAsync(tableId, schemaBundleId));
}
/**
- * Asynchronously deletes an schema bundle with the specified schema bundle ID in the specified
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#deleteSchemaBundleCallable()}.
+ *
+ * Asynchronously deletes an schema bundle with the specified schema bundle ID in the specified
* table.
*
*
Sample code:
@@ -2152,6 +2445,7 @@ public void deleteSchemaBundle(String tableId, String schemaBundleId) {
* );
* }
*/
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture deleteSchemaBundleAsync(String tableId, String schemaBundleId) {
DeleteSchemaBundleRequest request =
DeleteSchemaBundleRequest.newBuilder()
@@ -2202,7 +2496,10 @@ public Void apply(Empty empty) {
}
/**
- * Gets the IAM access control policy for the specified table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getIamPolicy(com.google.iam.v1.GetIamPolicyRequest)}.
+ *
+ * Gets the IAM access control policy for the specified table.
*
*
Sample code:
*
@@ -2218,12 +2515,16 @@ public Void apply(Empty empty) {
* IAM management
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public Policy getIamPolicy(String tableId) {
return ApiExceptions.callAndTranslateApiException(getIamPolicyAsync(tableId));
}
/**
- * Asynchronously gets the IAM access control policy for the specified table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getIamPolicyCallable()}.
+ *
+ *
Asynchronously gets the IAM access control policy for the specified table.
*
*
Sample code:
*
@@ -2250,13 +2551,17 @@ public Policy getIamPolicy(String tableId) {
* IAM management
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture getIamPolicyAsync(String tableId) {
String tableName = NameUtil.formatTableName(projectId, instanceId, tableId);
return getResourceIamPolicy(tableName);
}
/**
- * Replaces the IAM policy associated with the specified table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#setIamPolicy(com.google.iam.v1.SetIamPolicyRequest)}.
+ *
+ * Replaces the IAM policy associated with the specified table.
*
*
Sample code:
*
@@ -2273,12 +2578,16 @@ public ApiFuture getIamPolicyAsync(String tableId) {
* IAM management
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public Policy setIamPolicy(String tableId, Policy policy) {
return ApiExceptions.callAndTranslateApiException(setIamPolicyAsync(tableId, policy));
}
/**
- * Asynchronously replaces the IAM policy associated with the specified table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#setIamPolicyCallable()}.
+ *
+ * Asynchronously replaces the IAM policy associated with the specified table.
*
*
Sample code:
*
@@ -2309,14 +2618,18 @@ public Policy setIamPolicy(String tableId, Policy policy) {
* IAM management
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture setIamPolicyAsync(String tableId, Policy policy) {
String tableName = NameUtil.formatTableName(projectId, instanceId, tableId);
return setResourceIamPolicy(policy, tableName);
}
/**
- * Tests whether the caller has the given permissions for the specified table. Returns a subset of
- * the specified permissions that the caller has.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#testIamPermissions(com.google.iam.v1.TestIamPermissionsRequest)}.
+ *
+ * Tests whether the caller has the given permissions for the specified table. Returns a subset
+ * of the specified permissions that the caller has.
*
*
Sample code:
*
@@ -2333,12 +2646,16 @@ public ApiFuture setIamPolicyAsync(String tableId, Policy policy) {
* permissions
*/
@SuppressWarnings({"WeakerAccess"})
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public List testIamPermission(String tableId, String... permissions) {
return ApiExceptions.callAndTranslateApiException(testIamPermissionAsync(tableId, permissions));
}
/**
- * Asynchronously tests whether the caller has the given permissions for the specified table.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#testIamPermissionsCallable()}.
+ *
+ * Asynchronously tests whether the caller has the given permissions for the specified table.
* Returns a subset of the specified permissions that the caller has.
*
*
Sample code:
@@ -2365,13 +2682,17 @@ public List testIamPermission(String tableId, String... permissions) {
* permissions
*/
@SuppressWarnings({"WeakerAccess"})
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture> testIamPermissionAsync(String tableId, String... permissions) {
String tableName = NameUtil.formatTableName(projectId, instanceId, tableId);
return testResourceIamPermissions(tableName, permissions);
}
/**
- * Gets the IAM access control policy for the specified backup.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getIamPolicy(com.google.iam.v1.GetIamPolicyRequest)}.
+ *
+ * Gets the IAM access control policy for the specified backup.
*
*
Sample code:
*
@@ -2387,12 +2708,16 @@ public ApiFuture> testIamPermissionAsync(String tableId, String...
* IAM management
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public Policy getBackupIamPolicy(String clusterId, String backupId) {
return ApiExceptions.callAndTranslateApiException(getBackupIamPolicyAsync(clusterId, backupId));
}
/**
- * Asynchronously gets the IAM access control policy for the specified backup.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getIamPolicyCallable()}.
+ *
+ * Asynchronously gets the IAM access control policy for the specified backup.
*
*
Sample code:
*
@@ -2419,13 +2744,17 @@ public Policy getBackupIamPolicy(String clusterId, String backupId) {
* IAM management
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture getBackupIamPolicyAsync(String clusterId, String backupId) {
String backupName = NameUtil.formatBackupName(projectId, instanceId, clusterId, backupId);
return getResourceIamPolicy(backupName);
}
/**
- * Replaces the IAM policy associated with the specified backup.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#setIamPolicy(com.google.iam.v1.SetIamPolicyRequest)}.
+ *
+ * Replaces the IAM policy associated with the specified backup.
*
*
Sample code:
*
@@ -2442,13 +2771,17 @@ public ApiFuture getBackupIamPolicyAsync(String clusterId, String backup
* IAM management
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public Policy setBackupIamPolicy(String clusterId, String backupId, Policy policy) {
return ApiExceptions.callAndTranslateApiException(
setBackupIamPolicyAsync(clusterId, backupId, policy));
}
/**
- * Asynchronously replaces the IAM policy associated with the specified backup.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#setIamPolicyCallable()}.
+ *
+ * Asynchronously replaces the IAM policy associated with the specified backup.
*
*
Sample code:
*
@@ -2479,6 +2812,7 @@ public Policy setBackupIamPolicy(String clusterId, String backupId, Policy polic
* IAM management
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture setBackupIamPolicyAsync(
String clusterId, String backupId, Policy policy) {
String backupName = NameUtil.formatBackupName(projectId, instanceId, clusterId, backupId);
@@ -2486,8 +2820,11 @@ public ApiFuture setBackupIamPolicyAsync(
}
/**
- * Tests whether the caller has the given permissions for the specified backup. Returns a subset
- * of the specified permissions that the caller has.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#testIamPermissions(com.google.iam.v1.TestIamPermissionsRequest)}.
+ *
+ * Tests whether the caller has the given permissions for the specified backup. Returns a
+ * subset of the specified permissions that the caller has.
*
*
Sample code:
*
@@ -2506,6 +2843,7 @@ public ApiFuture setBackupIamPolicyAsync(
* permissions
*/
@SuppressWarnings({"WeakerAccess"})
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public List testBackupIamPermission(
String clusterId, String backupId, String... permissions) {
return ApiExceptions.callAndTranslateApiException(
@@ -2513,7 +2851,10 @@ public List testBackupIamPermission(
}
/**
- * Asynchronously tests whether the caller has the given permissions for the specified backup.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#testIamPermissionsCallable()}.
+ *
+ * Asynchronously tests whether the caller has the given permissions for the specified backup.
* Returns a subset of the specified permissions that the caller has.
*
*
Sample code:
@@ -2540,6 +2881,7 @@ public List testBackupIamPermission(
* permissions
*/
@SuppressWarnings({"WeakerAccess"})
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture> testBackupIamPermissionAsync(
String clusterId, String backupId, String... permissions) {
String backupName = NameUtil.formatBackupName(projectId, instanceId, clusterId, backupId);
@@ -2547,7 +2889,10 @@ public ApiFuture> testBackupIamPermissionAsync(
}
/**
- * Gets the IAM access control policy for the specified authorized view.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getIamPolicy(com.google.iam.v1.GetIamPolicyRequest)}.
+ *
+ * Gets the IAM access control policy for the specified authorized view.
*
*
Sample code:
*
@@ -2563,13 +2908,17 @@ public ApiFuture> testBackupIamPermissionAsync(
* IAM management
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public Policy getAuthorizedViewIamPolicy(String tableId, String authorizedViewId) {
return ApiExceptions.callAndTranslateApiException(
getAuthorizedViewIamPolicyAsync(tableId, authorizedViewId));
}
/**
- * Asynchronously gets the IAM access control policy for the specified authorized view.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#getIamPolicyCallable()}.
+ *
+ * Asynchronously gets the IAM access control policy for the specified authorized view.
*
*
Sample code:
*
@@ -2596,6 +2945,7 @@ public Policy getAuthorizedViewIamPolicy(String tableId, String authorizedViewId
* IAM management
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture getAuthorizedViewIamPolicyAsync(
String tableId, String authorizedViewId) {
String authorizedViewName =
@@ -2604,7 +2954,10 @@ public ApiFuture getAuthorizedViewIamPolicyAsync(
}
/**
- * Replaces the IAM policy associated with the specified authorized view.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#setIamPolicy(com.google.iam.v1.SetIamPolicyRequest)}.
+ *
+ * Replaces the IAM policy associated with the specified authorized view.
*
*
Sample code:
*
@@ -2621,13 +2974,17 @@ public ApiFuture getAuthorizedViewIamPolicyAsync(
* IAM management
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public Policy setAuthorizedViewIamPolicy(String tableId, String authorizedViewId, Policy policy) {
return ApiExceptions.callAndTranslateApiException(
setAuthorizedViewIamPolicyAsync(tableId, authorizedViewId, policy));
}
/**
- * Asynchronously replaces the IAM policy associated with the specified authorized view.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#setIamPolicyCallable()}.
+ *
+ * Asynchronously replaces the IAM policy associated with the specified authorized view.
*
*
Sample code:
*
@@ -2658,6 +3015,7 @@ public Policy setAuthorizedViewIamPolicy(String tableId, String authorizedViewId
* IAM management
*/
@SuppressWarnings("WeakerAccess")
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture setAuthorizedViewIamPolicyAsync(
String tableId, String authorizedViewId, Policy policy) {
String authorizedViewName =
@@ -2666,8 +3024,11 @@ public ApiFuture setAuthorizedViewIamPolicyAsync(
}
/**
- * Tests whether the caller has the given permissions for the specified authorized view. Returns a
- * subset of the specified permissions that the caller has.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#testIamPermissions(com.google.iam.v1.TestIamPermissionsRequest)}.
+ *
+ * Tests whether the caller has the given permissions for the specified authorized view.
+ * Returns a subset of the specified permissions that the caller has.
*
*
Sample code:
*
@@ -2686,6 +3047,7 @@ public ApiFuture setAuthorizedViewIamPolicyAsync(
* permissions
*/
@SuppressWarnings({"WeakerAccess"})
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public List testAuthorizedViewIamPermission(
String tableId, String authorizedViewId, String... permissions) {
return ApiExceptions.callAndTranslateApiException(
@@ -2693,14 +3055,17 @@ public List testAuthorizedViewIamPermission(
}
/**
- * Asynchronously tests whether the caller has the given permissions for the specified authorized
- * view. Returns a subset of the specified permissions that the caller has.
+ * This method is obsolete. For the recommended proto-based approach, please see {@link
+ * com.google.cloud.bigtable.admin.v2.BaseBigtableTableAdminClient#testIamPermissionsCallable()}.
+ *
+ * Asynchronously tests whether the caller has the given permissions for the specified
+ * authorized view. Returns a subset of the specified permissions that the caller has.
*
*
Sample code:
*
*
{@code
- * ApiFuture> grantedPermissionsFuture = client.testAuthorizedViewIamPermissionAsync("my-table-id", "my-authorized-view-id",
- * "bigtable.authorizedViews.get", "bigtable.authorizedViews.delete");
+ * ApiFuture> grantedPermissionsFuture = client.testAuthorizedViewIamPermissionAsync("my-table-id",
+ * "my-authorized-view-id", "bigtable.authorizedViews.get", "bigtable.authorizedViews.delete");
*
* ApiFutures.addCallback(grantedPermissionsFuture,
* new ApiFutureCallback>() {
@@ -2720,6 +3085,7 @@ public List testAuthorizedViewIamPermission(
* permissions
*/
@SuppressWarnings({"WeakerAccess"})
+ @ObsoleteApi("Use getBaseClient() to access the auto-generated proto-based methods instead.")
public ApiFuture> testAuthorizedViewIamPermissionAsync(
String tableId, String authorizedViewId, String... permissions) {
String authorizedViewName =
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/CreateInstanceRequest.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/CreateInstanceRequest.java
index 685e52d555..69c75f9011 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/CreateInstanceRequest.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/CreateInstanceRequest.java
@@ -118,6 +118,22 @@ public CreateInstanceRequest addLabel(@Nonnull String key, @Nonnull String value
return this;
}
+ /**
+ * Adds a tag to the instance.
+ *
+ * Tags are a way to organize and govern resources across Google Cloud. Unlike labels, Tags are
+ * standalone resources created and managed through the Resource Manager API.
+ *
+ * @see For more details
+ */
+ @SuppressWarnings("WeakerAccess")
+ public CreateInstanceRequest addTag(@Nonnull String key, @Nonnull String value) {
+ Preconditions.checkNotNull(key, "Key can't be null");
+ Preconditions.checkNotNull(value, "Value can't be null");
+ builder.getInstanceBuilder().putTags(key, value);
+ return this;
+ }
+
/**
* Adds a cluster to the instance request with manual scaling enabled.
*
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/Instance.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/Instance.java
index c3a0c43bca..df163b0e0d 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/Instance.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/admin/v2/models/Instance.java
@@ -150,6 +150,12 @@ public String getDisplayName() {
return proto.getDisplayName();
}
+ /** Gets the instance's tags. */
+ @SuppressWarnings("WeakerAccess")
+ public Map getTags() {
+ return proto.getTagsMap();
+ }
+
/** Gets the instance's current type. Can be DEVELOPMENT or PRODUCTION. */
@SuppressWarnings("WeakerAccess")
public Type getType() {
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClient.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClient.java
index cef5e58f3a..b659a02175 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClient.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClient.java
@@ -54,7 +54,6 @@
import com.google.cloud.bigtable.data.v2.models.sql.PreparedStatement;
import com.google.cloud.bigtable.data.v2.models.sql.ResultSet;
import com.google.cloud.bigtable.data.v2.models.sql.SqlType;
-import com.google.cloud.bigtable.data.v2.stub.BigtableClientContext;
import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStub;
import com.google.cloud.bigtable.data.v2.stub.sql.SqlServerStream;
import com.google.common.util.concurrent.MoreExecutors;
@@ -180,18 +179,6 @@ public static BigtableDataClient create(BigtableDataSettings settings) throws IO
return new BigtableDataClient(stub);
}
- /**
- * Constructs an instance of BigtableDataClient with the provided client context. This is used by
- * {@link BigtableDataClientFactory} and the client context will not be closed unless {@link
- * BigtableDataClientFactory#close()} is called.
- */
- static BigtableDataClient createWithClientContext(
- BigtableDataSettings settings, BigtableClientContext context) throws IOException {
- EnhancedBigtableStub stub =
- EnhancedBigtableStub.createWithClientContext(settings.getStubSettings(), context);
- return new BigtableDataClient(stub);
- }
-
@InternalApi("Visible for testing")
BigtableDataClient(EnhancedBigtableStub stub) {
this.stub = stub;
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactory.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactory.java
index 599dce9f31..f19726e2a3 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactory.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactory.java
@@ -16,8 +16,9 @@
package com.google.cloud.bigtable.data.v2;
import com.google.api.core.BetaApi;
-import com.google.api.gax.rpc.ClientContext;
+import com.google.cloud.bigtable.data.v2.internal.api.InstanceName;
import com.google.cloud.bigtable.data.v2.stub.BigtableClientContext;
+import com.google.cloud.bigtable.data.v2.stub.ClientOperationSettings;
import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStub;
import java.io.IOException;
import javax.annotation.Nonnull;
@@ -62,9 +63,8 @@
*/
@BetaApi("This feature is currently experimental and can change in the future")
public final class BigtableDataClientFactory implements AutoCloseable {
-
- private final BigtableDataSettings defaultSettings;
private final BigtableClientContext sharedClientContext;
+ private final ClientOperationSettings perOpSettings;
/**
* Create a instance of this factory.
@@ -75,15 +75,15 @@ public final class BigtableDataClientFactory implements AutoCloseable {
public static BigtableDataClientFactory create(BigtableDataSettings defaultSettings)
throws IOException {
BigtableClientContext sharedClientContext =
- EnhancedBigtableStub.createBigtableClientContext(defaultSettings.getStubSettings());
-
- return new BigtableDataClientFactory(sharedClientContext, defaultSettings);
+ BigtableClientContext.create(defaultSettings.getStubSettings());
+ ClientOperationSettings perOpSettings = defaultSettings.getStubSettings().getPerOpSettings();
+ return new BigtableDataClientFactory(sharedClientContext, perOpSettings);
}
private BigtableDataClientFactory(
- BigtableClientContext sharedClientContext, BigtableDataSettings defaultSettings) {
+ BigtableClientContext sharedClientContext, ClientOperationSettings perOpSettings) {
this.sharedClientContext = sharedClientContext;
- this.defaultSettings = defaultSettings;
+ this.perOpSettings = perOpSettings;
}
/**
@@ -107,15 +107,12 @@ public void close() throws Exception {
*/
public BigtableDataClient createDefault() {
try {
- ClientContext clientContext =
- sharedClientContext.getClientContext().toBuilder()
- .setTracerFactory(
- EnhancedBigtableStub.createBigtableTracerFactory(
- defaultSettings.getStubSettings(), sharedClientContext.getOpenTelemetry()))
- .build();
+ BigtableClientContext ctx =
+ sharedClientContext.createChild(
+ sharedClientContext.getClientInfo().getInstanceName(),
+ sharedClientContext.getClientInfo().getAppProfileId());
- return BigtableDataClient.createWithClientContext(
- defaultSettings, sharedClientContext.withClientContext(clientContext));
+ return new BigtableDataClient(new EnhancedBigtableStub(perOpSettings, ctx));
} catch (IOException e) {
// Should never happen because the connection has been established already
throw new RuntimeException(
@@ -133,17 +130,11 @@ public BigtableDataClient createDefault() {
* release all resources, first close all of the created clients and then this factory instance.
*/
public BigtableDataClient createForAppProfile(@Nonnull String appProfileId) throws IOException {
- BigtableDataSettings settings =
- defaultSettings.toBuilder().setAppProfileId(appProfileId).build();
+ BigtableClientContext ctx =
+ sharedClientContext.createChild(
+ sharedClientContext.getClientInfo().getInstanceName(), appProfileId);
- ClientContext clientContext =
- sharedClientContext.getClientContext().toBuilder()
- .setTracerFactory(
- EnhancedBigtableStub.createBigtableTracerFactory(
- settings.getStubSettings(), sharedClientContext.getOpenTelemetry()))
- .build();
- return BigtableDataClient.createWithClientContext(
- settings, sharedClientContext.withClientContext(clientContext));
+ return new BigtableDataClient(new EnhancedBigtableStub(perOpSettings, ctx));
}
/**
@@ -157,22 +148,10 @@ public BigtableDataClient createForAppProfile(@Nonnull String appProfileId) thro
*/
public BigtableDataClient createForInstance(@Nonnull String projectId, @Nonnull String instanceId)
throws IOException {
- BigtableDataSettings settings =
- defaultSettings.toBuilder()
- .setProjectId(projectId)
- .setInstanceId(instanceId)
- .setDefaultAppProfileId()
- .build();
+ BigtableClientContext ctx =
+ sharedClientContext.createChild(InstanceName.of(projectId, instanceId), "");
- ClientContext clientContext =
- sharedClientContext.getClientContext().toBuilder()
- .setTracerFactory(
- EnhancedBigtableStub.createBigtableTracerFactory(
- settings.getStubSettings(), sharedClientContext.getOpenTelemetry()))
- .build();
-
- return BigtableDataClient.createWithClientContext(
- settings, sharedClientContext.withClientContext(clientContext));
+ return new BigtableDataClient(new EnhancedBigtableStub(perOpSettings, ctx));
}
/**
@@ -187,19 +166,9 @@ public BigtableDataClient createForInstance(@Nonnull String projectId, @Nonnull
public BigtableDataClient createForInstance(
@Nonnull String projectId, @Nonnull String instanceId, @Nonnull String appProfileId)
throws IOException {
- BigtableDataSettings settings =
- defaultSettings.toBuilder()
- .setProjectId(projectId)
- .setInstanceId(instanceId)
- .setAppProfileId(appProfileId)
- .build();
- ClientContext clientContext =
- sharedClientContext.getClientContext().toBuilder()
- .setTracerFactory(
- EnhancedBigtableStub.createBigtableTracerFactory(
- settings.getStubSettings(), sharedClientContext.getOpenTelemetry()))
- .build();
- return BigtableDataClient.createWithClientContext(
- settings, sharedClientContext.withClientContext(clientContext));
+ BigtableClientContext ctx =
+ sharedClientContext.createChild(InstanceName.of(projectId, instanceId), appProfileId);
+
+ return new BigtableDataClient(new EnhancedBigtableStub(perOpSettings, ctx));
}
}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java
index b8a514433f..4329e98f63 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/BigtableDataSettings.java
@@ -450,7 +450,6 @@ public boolean isRefreshingChannel() {
*/
@Deprecated
public Builder setPrimingTableIds(String... tableIds) {
- stubSettings.setPrimedTableIds(tableIds);
return this;
}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/RequestContext.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/RequestContext.java
index fc015186aa..7058ae137c 100644
--- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/RequestContext.java
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/RequestContext.java
@@ -17,6 +17,7 @@
import com.google.api.core.InternalApi;
import com.google.auto.value.AutoValue;
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo;
import java.io.Serializable;
/**
@@ -33,6 +34,13 @@
@AutoValue
public abstract class RequestContext implements Serializable {
+ public static RequestContext create(ClientInfo clientInfo) {
+ return create(
+ clientInfo.getInstanceName().getProjectId(),
+ clientInfo.getInstanceName().getInstanceId(),
+ clientInfo.getAppProfileId());
+ }
+
/** Creates a new instance of the {@link RequestContext}. */
public static RequestContext create(String projectId, String instanceId, String appProfileId) {
return new AutoValue_RequestContext(projectId, instanceId, appProfileId);
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/api/InstanceName.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/api/InstanceName.java
new file mode 100644
index 0000000000..01dfed2d72
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/api/InstanceName.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2025 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
+ *
+ * https://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.bigtable.data.v2.internal.api;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import java.util.List;
+
+@AutoValue
+public abstract class InstanceName {
+ public abstract String getProjectId();
+
+ public abstract String getInstanceId();
+
+ @Override
+ public final String toString() {
+ return String.format("projects/%s/instances/%s", getProjectId(), getInstanceId());
+ }
+
+ public static InstanceName of(String projectId, String instanceId) {
+ return InstanceName.builder().setProjectId(projectId).setInstanceId(instanceId).build();
+ }
+
+ public static Builder builder() {
+ return new AutoValue_InstanceName.Builder();
+ }
+
+ public static InstanceName parse(String name) {
+ List parts = Splitter.on('/').splitToList(name);
+ Preconditions.checkArgument(parts.size() == 4, "Invalid instance name: %s", name);
+ Preconditions.checkArgument(
+ "projects".equals(parts.get(0)),
+ "Invalid instance name: %s, must start with projects/",
+ name);
+ Preconditions.checkArgument(
+ !parts.get(1).isEmpty(), "Invalid instance name %s, must have a project id", name);
+ Preconditions.checkArgument(
+ "instances".equals(parts.get(2)),
+ "Invalid instance name: %s, must start with projects/$PROJECT_ID/instances/",
+ name);
+ Preconditions.checkArgument(
+ !parts.get(3).isEmpty(), "Invalid instance name %s, must have an instance id", name);
+
+ return builder().setProjectId(parts.get(1)).setInstanceId(parts.get(3)).build();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setProjectId(String projectId);
+
+ public abstract Builder setInstanceId(String instanceId);
+
+ public abstract InstanceName build();
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/api/TableName.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/api/TableName.java
new file mode 100644
index 0000000000..159c7b0b50
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/api/TableName.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2025 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
+ *
+ * https://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.bigtable.data.v2.internal.api;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import java.util.List;
+
+@AutoValue
+public abstract class TableName {
+ public abstract String getProjectId();
+
+ public abstract String getInstanceId();
+
+ public abstract String getTableId();
+
+ public InstanceName getInstanceName() {
+ return InstanceName.builder()
+ .setProjectId(getProjectId())
+ .setInstanceId(getInstanceId())
+ .build();
+ }
+
+ @Override
+ public final String toString() {
+ return String.format("%s/tables/%s", getInstanceName(), getTableId());
+ }
+
+ public static Builder builder() {
+ return new AutoValue_TableName.Builder();
+ }
+
+ public static TableName parse(String name) {
+ List parts = Splitter.on('/').splitToList(name);
+ Preconditions.checkArgument(parts.size() == 6, "Invalid table name: %s", name);
+ Preconditions.checkArgument(
+ "projects".equals(parts.get(0)), "Invalid table name: %s, must start with projects/", name);
+ Preconditions.checkArgument(
+ !parts.get(1).isEmpty(), "Invalid table name %s, must have a project id", name);
+ Preconditions.checkArgument(
+ "instances".equals(parts.get(2)),
+ "Invalid table name: %s, must start with projects/$PROJECT_ID/instances/",
+ name);
+ Preconditions.checkArgument(
+ !parts.get(3).isEmpty(), "Invalid table name %s, must have an instance id", name);
+ Preconditions.checkArgument(
+ "tables".equals(parts.get(4)),
+ "Invalid table name: %s, must start with"
+ + " projects/$PROJECT_ID/instances/$INSTANCE_ID/tables",
+ name);
+ Preconditions.checkArgument(
+ !parts.get(5).isEmpty(), "Invalid table name %s, must have table id", name);
+
+ return builder()
+ .setProjectId(parts.get(1))
+ .setInstanceId(parts.get(3))
+ .setTableId(parts.get(5))
+ .build();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setProjectId(String projectId);
+
+ public abstract Builder setInstanceId(String instanceId);
+
+ public abstract Builder setTableId(String tableId);
+
+ public abstract TableName build();
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistry.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistry.java
new file mode 100644
index 0000000000..9c0a70d30c
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricRegistry.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2025 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
+ *
+ * https://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.bigtable.data.v2.internal.csm;
+
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientBatchWriteFlowControlFactor;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientBatchWriteFlowControlTargetQps;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientChannelPoolOutstandingRpcs;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientDpCompatGuage;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.ClientPerConnectionErrorCount;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.GrpcMetric;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.MetricWrapper;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.PacemakerDelay;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableApplicationBlockingLatency;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableAttemptLatency;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableAttemptLatency2;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableClientBlockingLatency;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableConnectivityErrorCount;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableDebugTagCount;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableFirstResponseLatency;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableOperationLatency;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableRemainingDeadline;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableRetryCount;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.TableServerLatency;
+import com.google.common.collect.ImmutableList;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.api.metrics.MeterProvider;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Repository for all client metrics. This class has 2 audiences:
+ *
+ *
+ * VRpcTracer, which reference each metric directly
+ * Exporter, which will look up each metric by name and use the {@link MetricWrapper}
+ * interface to augment the {@code MonitoredResource} and {@code Metric Labels}
+ *
+ */
+public class MetricRegistry {
+ public static final String METER_NAME = "bigtable.googleapis.com/internal/client/";
+
+ final TableOperationLatency operationLatencyMetric;
+ final TableAttemptLatency attemptLatencyMetric;
+ final TableAttemptLatency2 attemptLatency2Metric;
+ final TableRetryCount retryCountMetric;
+ final TableFirstResponseLatency firstResponseLantencyMetric;
+ final TableServerLatency serverLatencyMetric;
+ final ClientChannelPoolOutstandingRpcs channelPoolOutstandingRpcsMetric;
+ final TableConnectivityErrorCount connectivityErrorCountMetric;
+ final ClientDpCompatGuage dpCompatGuageMetric;
+ final TableApplicationBlockingLatency applicationBlockingLatencyMetric;
+ final TableClientBlockingLatency clientBlockingLatencyMetric;
+ final ClientPerConnectionErrorCount perConnectionErrorCountMetric;
+ final TableRemainingDeadline remainingDeadlineMetric;
+ final ClientBatchWriteFlowControlFactor batchWriteFlowControlFactorMetric;
+ final ClientBatchWriteFlowControlTargetQps batchWriteFlowControlTargetQpsMetric;
+
+ final TableDebugTagCount debugTagCountMetric;
+ final PacemakerDelay pacemakerDelayMetric;
+
+ private final Map> metrics = new HashMap<>();
+ private final List grpcMetricNames = new ArrayList<>();
+
+ public MetricRegistry() {
+ operationLatencyMetric = register(new TableOperationLatency());
+ attemptLatencyMetric = register(new TableAttemptLatency());
+ attemptLatency2Metric = register(new TableAttemptLatency2());
+ retryCountMetric = register(new TableRetryCount());
+ firstResponseLantencyMetric = register(new TableFirstResponseLatency());
+ serverLatencyMetric = register(new TableServerLatency());
+ channelPoolOutstandingRpcsMetric = register(new ClientChannelPoolOutstandingRpcs());
+ connectivityErrorCountMetric = register(new TableConnectivityErrorCount());
+ applicationBlockingLatencyMetric = register(new TableApplicationBlockingLatency());
+ clientBlockingLatencyMetric = register(new TableClientBlockingLatency());
+ perConnectionErrorCountMetric = register(new ClientPerConnectionErrorCount());
+ dpCompatGuageMetric = register(new ClientDpCompatGuage());
+ remainingDeadlineMetric = register(new TableRemainingDeadline());
+ batchWriteFlowControlFactorMetric = register(new ClientBatchWriteFlowControlFactor());
+ batchWriteFlowControlTargetQpsMetric = register(new ClientBatchWriteFlowControlTargetQps());
+
+ debugTagCountMetric = register(new TableDebugTagCount());
+ pacemakerDelayMetric = register(new PacemakerDelay());
+
+ // From
+ // https://github.com/grpc/grpc-java/blob/31fdb6c2268b4b1c8ba6c995ee46c58e84a831aa/rls/src/main/java/io/grpc/rls/CachingRlsLbClient.java#L138-L165
+ registerGrpcMetric(
+ "grpc.client.attempt.duration",
+ ImmutableList.of("grpc.lb.locality", "grpc.status", "grpc.method", "grpc.target"));
+ registerGrpcMetric(
+ "grpc.lb.rls.default_target_picks",
+ ImmutableList.of(
+ "grpc.target",
+ "grpc.lb.rls.server_target",
+ "grpc.lb.rls.data_plane_target",
+ "grpc.lb.pick_result"));
+ registerGrpcMetric(
+ "grpc.lb.rls.target_picks",
+ ImmutableList.of(
+ "grpc.target",
+ "grpc.lb.rls.server_target",
+ "grpc.lb.rls.data_plane_target",
+ "grpc.lb.pick_result"));
+ registerGrpcMetric(
+ "grpc.lb.rls.failed_picks", ImmutableList.of("grpc.target", "grpc.lb.rls.server_target"));
+
+ // From
+ // https://github.com/grpc/grpc-java/blob/31fdb6c2268b4b1c8ba6c995ee46c58e84a831aa/xds/src/main/java/io/grpc/xds/XdsClientMetricReporterImpl.java#L67-L94
+ // TODO: "grpc.xds_client.connected"
+ registerGrpcMetric(
+ "grpc.xds_client.server_failure", ImmutableList.of("grpc.target", "grpc.xds.server"));
+ // TODO: "grpc.xds_client.resource_updates_valid",
+ registerGrpcMetric(
+ "grpc.xds_client.resource_updates_invalid",
+ ImmutableList.of("grpc.target", "grpc.xds.server", "grpc.xds.resource_type"));
+ // TODO: "grpc.xds_client.resources"
+
+ // From
+ // https://github.com/grpc/proposal/blob/86990145a7cef9e5473a132709b2556fec00c4c6/A94-subchannel-otel-metrics.md
+ registerGrpcMetric(
+ "grpc.subchannel.disconnections",
+ ImmutableList.of(
+ "grpc.target", "grpc.lb.backend_service", "grpc.lb.locality", "grpc.disconnect_error"));
+
+ registerGrpcMetric(
+ "grpc.subchannel.connection_attempts_succeeded",
+ ImmutableList.of("grpc.target", "grpc.lb.backend_service", "grpc.lb.locality"));
+
+ registerGrpcMetric(
+ "grpc.subchannel.connection_attempts_failed",
+ ImmutableList.of("grpc.target", "grpc.lb.backend_service", "grpc.lb.locality"));
+
+ registerGrpcMetric(
+ "grpc.subchannel.open_connections",
+ ImmutableList.of(
+ "grpc.target", "grpc.security_level", "grpc.lb.backend_service", "grpc.lb.locality"));
+ }
+
+ private void registerGrpcMetric(String name, List labels) {
+ grpcMetricNames.add(name);
+ register(new GrpcMetric(name, labels));
+ }
+
+ private > T register(T instrument) {
+ metrics.put(instrument.getName(), instrument);
+ return instrument;
+ }
+
+ List getGrpcMetricNames() {
+ return ImmutableList.copyOf(grpcMetricNames);
+ }
+
+ public MetricWrapper> getMetric(String name) {
+ return metrics.get(name);
+ }
+
+ public RecorderRegistry newRecorderRegistry(MeterProvider meterProvider) {
+ return new RecorderRegistry(meterProvider.get(METER_NAME));
+ }
+
+ public class RecorderRegistry {
+ public final TableOperationLatency.Recorder operationLatency;
+ public final TableAttemptLatency.Recorder attemptLatency;
+ public final TableAttemptLatency2.Recorder attemptLatency2;
+ public final TableRetryCount.Recorder retryCount;
+ public final TableFirstResponseLatency.Recorder firstResponseLantency;
+ public final TableServerLatency.Recorder serverLatency;
+ public final ClientChannelPoolOutstandingRpcs.Recorder channelPoolOutstandingRpcs;
+ public final TableConnectivityErrorCount.Recorder connectivityErrorCount;
+ public final ClientDpCompatGuage.Recorder dpCompatGuage;
+ public final TableApplicationBlockingLatency.Recorder applicationBlockingLatency;
+ public final TableClientBlockingLatency.Recorder clientBlockingLatency;
+ public final ClientPerConnectionErrorCount.Recorder perConnectionErrorCount;
+ public final TableRemainingDeadline.Recorder remainingDeadline;
+ public final ClientBatchWriteFlowControlTargetQps.Recorder batchWriteFlowControlTargetQps;
+ public final ClientBatchWriteFlowControlFactor.Recorder batchWriteFlowControlFactor;
+
+ public final TableDebugTagCount.Recorder debugTagCount;
+
+ public final PacemakerDelay.Recorder pacemakerDelay;
+
+ private RecorderRegistry(Meter meter) {
+ operationLatency = operationLatencyMetric.newRecorder(meter);
+ attemptLatency = attemptLatencyMetric.newRecorder(meter);
+ attemptLatency2 = attemptLatency2Metric.newRecorder(meter);
+ retryCount = retryCountMetric.newRecorder(meter);
+ firstResponseLantency = firstResponseLantencyMetric.newRecorder(meter);
+ serverLatency = serverLatencyMetric.newRecorder(meter);
+ channelPoolOutstandingRpcs = channelPoolOutstandingRpcsMetric.newRecorder(meter);
+ connectivityErrorCount = connectivityErrorCountMetric.newRecorder(meter);
+ dpCompatGuage = dpCompatGuageMetric.newRecorder(meter);
+ applicationBlockingLatency = applicationBlockingLatencyMetric.newRecorder(meter);
+ clientBlockingLatency = clientBlockingLatencyMetric.newRecorder(meter);
+ perConnectionErrorCount = perConnectionErrorCountMetric.newRecorder(meter);
+ remainingDeadline = remainingDeadlineMetric.newRecorder(meter);
+ batchWriteFlowControlTargetQps = batchWriteFlowControlTargetQpsMetric.newRecorder(meter);
+ batchWriteFlowControlFactor = batchWriteFlowControlFactorMetric.newRecorder(meter);
+
+ debugTagCount = debugTagCountMetric.newRecorder(meter);
+ pacemakerDelay = pacemakerDelayMetric.newRecorder(meter);
+ }
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/Metrics.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/Metrics.java
new file mode 100644
index 0000000000..7df665c673
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/Metrics.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2026 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
+ *
+ * https://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.bigtable.data.v2.internal.csm;
+
+import com.google.api.gax.tracing.ApiTracerFactory;
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.tracers.ChannelPoolMetricsTracer;
+import io.grpc.ManagedChannelBuilder;
+import java.io.Closeable;
+import java.io.IOException;
+import javax.annotation.Nullable;
+
+public interface Metrics extends Closeable {
+ ApiTracerFactory createTracerFactory(ClientInfo clientInfo) throws IOException;
+
+ > T configureGrpcChannel(T channelBuilder);
+
+ @Nullable
+ ChannelPoolMetricsTracer getChannelPoolMetricsTracer();
+
+ void start();
+
+ @Override
+ void close();
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricsImpl.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricsImpl.java
new file mode 100644
index 0000000000..f0efac7e96
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/MetricsImpl.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2026 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
+ *
+ * https://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.bigtable.data.v2.internal.csm;
+
+import com.google.api.gax.grpc.GaxGrpcProperties;
+import com.google.api.gax.tracing.ApiTracerFactory;
+import com.google.api.gax.tracing.OpencensusTracerFactory;
+import com.google.auth.Credentials;
+import com.google.cloud.bigtable.Version;
+import com.google.cloud.bigtable.data.v2.BigtableDataSettings;
+import com.google.cloud.bigtable.data.v2.internal.csm.MetricRegistry.RecorderRegistry;
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.EnvInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.exporter.BigtableCloudMonitoringExporter;
+import com.google.cloud.bigtable.data.v2.internal.csm.exporter.BigtablePeriodicReader;
+import com.google.cloud.bigtable.data.v2.internal.csm.opencensus.MetricsTracerFactory;
+import com.google.cloud.bigtable.data.v2.internal.csm.opencensus.RpcMeasureConstants;
+import com.google.cloud.bigtable.data.v2.internal.csm.tracers.BuiltinMetricsTracerFactory;
+import com.google.cloud.bigtable.data.v2.internal.csm.tracers.ChannelPoolMetricsTracer;
+import com.google.cloud.bigtable.data.v2.internal.csm.tracers.CompositeTracerFactory;
+import com.google.cloud.bigtable.data.v2.internal.csm.tracers.Pacemaker;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.opentelemetry.GrpcOpenTelemetry;
+import io.opencensus.stats.StatsRecorder;
+import io.opencensus.tags.TagKey;
+import io.opencensus.tags.TagValue;
+import io.opencensus.tags.Tagger;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import javax.annotation.Nullable;
+
+public class MetricsImpl implements Metrics, Closeable {
+ private final MetricRegistry metricRegistry;
+
+ private final ApiTracerFactory userTracerFactory;
+ private final @Nullable OpenTelemetrySdk internalOtel;
+ private final @Nullable MetricRegistry.RecorderRegistry internalRecorder;
+ private final @Nullable OpenTelemetry userOtel;
+ private final @Nullable MetricRegistry.RecorderRegistry userRecorder;
+ private final ScheduledExecutorService executor;
+ private final Tagger ocTagger;
+ private final StatsRecorder ocRecorder;
+
+ @Nullable private final GrpcOpenTelemetry grpcOtel;
+ @Nullable private final ChannelPoolMetricsTracer channelPoolMetricsTracer;
+ @Nullable private final Pacemaker pacemaker;
+ private final List> tasks = new ArrayList<>();
+
+ public MetricsImpl(
+ MetricRegistry metricRegistry,
+ ClientInfo clientInfo,
+ ApiTracerFactory userTracerFactory,
+ @Nullable OpenTelemetrySdk internalOtel,
+ @Nullable OpenTelemetry userOtel,
+ Tagger ocTagger,
+ StatsRecorder ocRecorder,
+ ScheduledExecutorService executor) {
+ this.metricRegistry = metricRegistry;
+ this.userTracerFactory = Preconditions.checkNotNull(userTracerFactory);
+
+ this.internalOtel = internalOtel;
+ this.userOtel = userOtel;
+
+ this.ocTagger = ocTagger;
+ this.ocRecorder = ocRecorder;
+
+ this.executor = executor;
+
+ if (internalOtel != null) {
+ this.internalRecorder = metricRegistry.newRecorderRegistry(internalOtel.getMeterProvider());
+ this.pacemaker = new Pacemaker(internalRecorder, clientInfo, "background");
+ this.channelPoolMetricsTracer = new ChannelPoolMetricsTracer(internalRecorder, clientInfo);
+ this.grpcOtel =
+ GrpcOpenTelemetry.newBuilder()
+ .sdk(internalOtel)
+ .addOptionalLabel("grpc.lb.locality")
+ // Disable default grpc metrics
+ .disableAllMetrics()
+ // Enable specific grpc metrics
+ .enableMetrics(metricRegistry.getGrpcMetricNames())
+ .build();
+
+ } else {
+ this.internalRecorder = null;
+ this.grpcOtel = null;
+ this.pacemaker = null;
+ this.channelPoolMetricsTracer = null;
+ }
+
+ if (userOtel != null) {
+ this.userRecorder = metricRegistry.newRecorderRegistry(userOtel.getMeterProvider());
+ } else {
+ this.userRecorder = null;
+ }
+ }
+
+ @Override
+ public void close() {
+ for (ScheduledFuture> task : tasks) {
+ task.cancel(false);
+ }
+ if (internalOtel != null) {
+ internalOtel.close();
+ }
+ }
+
+ @Override
+ public void start() {
+ if (channelPoolMetricsTracer != null) {
+ tasks.add(channelPoolMetricsTracer.start(executor));
+ }
+ if (pacemaker != null) {
+ tasks.add(pacemaker.start(executor));
+ }
+ }
+
+ @Override
+ public > T configureGrpcChannel(T channelBuilder) {
+ if (grpcOtel == null) {
+ return channelBuilder;
+ }
+ grpcOtel.configureChannelBuilder(channelBuilder);
+ return channelBuilder;
+ }
+
+ @Override
+ public ApiTracerFactory createTracerFactory(ClientInfo clientInfo) {
+ ImmutableList.Builder tracerFactories = ImmutableList.builder();
+ tracerFactories
+ .add(createOCTracingFactory(clientInfo))
+ .add(createOCMetricsFactory(clientInfo, ocTagger, ocRecorder))
+ .add(userTracerFactory);
+
+ if (internalRecorder != null) {
+ tracerFactories.add(createOtelMetricsFactory(internalRecorder, clientInfo));
+ }
+ if (userRecorder != null) {
+ tracerFactories.add(createOtelMetricsFactory(userRecorder, clientInfo));
+ }
+
+ return new CompositeTracerFactory(tracerFactories.build());
+ }
+
+ @Override
+ @Nullable
+ public ChannelPoolMetricsTracer getChannelPoolMetricsTracer() {
+ return channelPoolMetricsTracer;
+ }
+
+ public static OpenTelemetrySdk createBuiltinOtel(
+ MetricRegistry metricRegistry,
+ ClientInfo clientInfo,
+ @Nullable Credentials defaultCredentials,
+ @Nullable String metricsEndpoint,
+ String universeDomain,
+ ScheduledExecutorService executor)
+ throws IOException {
+
+ Credentials credentials =
+ BigtableDataSettings.getMetricsCredentials() != null
+ ? BigtableDataSettings.getMetricsCredentials()
+ : defaultCredentials;
+
+ SdkMeterProviderBuilder meterProvider = SdkMeterProvider.builder();
+
+ BigtableCloudMonitoringExporter exporter =
+ BigtableCloudMonitoringExporter.create(
+ metricRegistry,
+ EnvInfo::detect,
+ clientInfo,
+ credentials,
+ metricsEndpoint,
+ universeDomain);
+
+ meterProvider.registerMetricReader(new BigtablePeriodicReader(exporter, executor));
+
+ return OpenTelemetrySdk.builder().setMeterProvider(meterProvider.build()).build();
+ }
+
+ private static ApiTracerFactory createOCTracingFactory(ClientInfo clientInfo) {
+ return new OpencensusTracerFactory(
+ ImmutableMap.builder()
+ // Annotate traces with the same tags as metrics
+ .put(
+ RpcMeasureConstants.BIGTABLE_PROJECT_ID.getName(),
+ clientInfo.getInstanceName().getProjectId())
+ .put(
+ RpcMeasureConstants.BIGTABLE_INSTANCE_ID.getName(),
+ clientInfo.getInstanceName().getInstanceId())
+ .put(
+ RpcMeasureConstants.BIGTABLE_APP_PROFILE_ID.getName(), clientInfo.getAppProfileId())
+ // Also annotate traces with library versions
+ .put("gax", GaxGrpcProperties.getGaxGrpcVersion())
+ .put("grpc", GaxGrpcProperties.getGrpcVersion())
+ .put("gapic", Version.VERSION)
+ .build());
+ }
+
+ private static ApiTracerFactory createOCMetricsFactory(
+ ClientInfo clientInfo, Tagger tagger, StatsRecorder stats) {
+
+ ImmutableMap attributes =
+ ImmutableMap.builder()
+ .put(
+ RpcMeasureConstants.BIGTABLE_PROJECT_ID,
+ TagValue.create(clientInfo.getInstanceName().getProjectId()))
+ .put(
+ RpcMeasureConstants.BIGTABLE_INSTANCE_ID,
+ TagValue.create(clientInfo.getInstanceName().getInstanceId()))
+ .put(
+ RpcMeasureConstants.BIGTABLE_APP_PROFILE_ID,
+ TagValue.create(clientInfo.getAppProfileId()))
+ .build();
+ return MetricsTracerFactory.create(tagger, stats, attributes);
+ }
+
+ private static BuiltinMetricsTracerFactory createOtelMetricsFactory(
+ RecorderRegistry recorder, ClientInfo clientInfo) {
+
+ return BuiltinMetricsTracerFactory.create(recorder, clientInfo);
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/attributes/ClientInfo.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/attributes/ClientInfo.java
new file mode 100644
index 0000000000..7122cb40c7
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/attributes/ClientInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2025 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
+ *
+ * https://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.bigtable.data.v2.internal.csm.attributes;
+
+import com.google.auto.value.AutoValue;
+import com.google.cloud.bigtable.Version;
+import com.google.cloud.bigtable.data.v2.internal.api.InstanceName;
+
+/**
+ * A value class to capture parameters that the client was instantiated with. These parameters will
+ * be used by the Exporter to derive MonitoredResource for GrpcMetrics.
+ */
+@AutoValue
+public abstract class ClientInfo {
+ /** The name and version of the client. */
+ public abstract String getClientName();
+
+ /** A unique identifier to disambiguate TimeSeries from multiple processes on the same VM. */
+ public abstract InstanceName getInstanceName();
+
+ public abstract String getAppProfileId();
+
+ public abstract Builder toBuilder();
+
+ public static Builder builder() {
+ return new AutoValue_ClientInfo.Builder().setClientName("java-bigtable/" + Version.VERSION);
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setClientName(String name);
+
+ public abstract Builder setInstanceName(InstanceName name);
+
+ public abstract Builder setAppProfileId(String appProfileId);
+
+ public abstract ClientInfo build();
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/attributes/EnvInfo.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/attributes/EnvInfo.java
new file mode 100644
index 0000000000..b7afb73ee9
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/attributes/EnvInfo.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2025 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
+ *
+ * https://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.bigtable.data.v2.internal.csm.attributes;
+
+import com.google.auto.value.AutoValue;
+import com.google.cloud.opentelemetry.detection.AttributeKeys;
+import com.google.cloud.opentelemetry.detection.DetectedPlatform;
+import com.google.cloud.opentelemetry.detection.GCPPlatformDetector;
+import com.google.common.base.Function;
+import com.google.common.base.Splitter;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
+/**
+ * Environment attributes, lazily extracted by the Exporter.
+ *
+ * The information will be extracted from the GCE metadata service and environment.
+ */
+@AutoValue
+public abstract class EnvInfo {
+ private static final Logger logger = Logger.getLogger(EnvInfo.class.getName());
+
+ private static final Map SUPPORTED_PLATFORM_MAP =
+ ImmutableMap.of(
+ GCPPlatformDetector.SupportedPlatform.GOOGLE_COMPUTE_ENGINE, "gcp_compute_engine",
+ GCPPlatformDetector.SupportedPlatform.GOOGLE_KUBERNETES_ENGINE, "gcp_kubernetes_engine");
+
+ private static final AtomicLong uidSuffix = new AtomicLong(0);
+
+ public abstract String getUid();
+
+ /** The Google platform running this client. ie. gcp_compute_engine */
+ public abstract String getPlatform();
+
+ /** The Google project that the VM belongs to. */
+ public abstract String getProject();
+
+ /** The geographic region that the VM is located in. */
+ public abstract String getRegion();
+
+ /** The numeric GCE vm instance id. */
+ public abstract String getHostId();
+
+ /** The hostname of the vm or container running the client. For gke, this will be the pod name. */
+ public abstract String getHostName();
+
+ public static Builder builder() {
+ return new AutoValue_EnvInfo.Builder().setUid(computeUid() + "-" + uidSuffix.getAndIncrement());
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setUid(String uid);
+
+ public abstract Builder setPlatform(String platform);
+
+ public abstract Builder setProject(String project);
+
+ public abstract Builder setRegion(String region);
+
+ public abstract Builder setHostId(String hostId);
+
+ public abstract Builder setHostName(String hostName);
+
+ public abstract EnvInfo build();
+ }
+
+ private static String computeUid() {
+ final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
+ // If jvm doesn't have the expected format, fallback to the local hostname
+ if (jvmName.indexOf('@') < 1) {
+ String hostname = "localhost";
+ try {
+ hostname = InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException e) {
+ logger.log(Level.INFO, "Unable to get the hostname.", e);
+ }
+ // Generate a random number and use the same format "random_number@hostname".
+ return "java-" + UUID.randomUUID() + "@" + hostname;
+ }
+ return "java-" + UUID.randomUUID() + jvmName;
+ }
+
+ public static EnvInfo detect() {
+ return detect(
+ GCPPlatformDetector.DEFAULT_INSTANCE.detectPlatform(),
+ System::getenv,
+ () -> {
+ try {
+ return InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ @Nullable
+ static EnvInfo detect(
+ DetectedPlatform detectedPlatform,
+ Function envGetter,
+ Supplier hostnameSupplier) {
+ @Nullable
+ String cloud_platform = SUPPORTED_PLATFORM_MAP.get(detectedPlatform.getSupportedPlatform());
+ if (cloud_platform == null) {
+ return EnvInfo.builder()
+ .setPlatform("unknown")
+ .setHostName(detectHostname(envGetter, hostnameSupplier))
+ .setRegion("global")
+ .setProject("")
+ .setHostId("")
+ .build();
+ }
+
+ Map attrs = detectedPlatform.getAttributes();
+ ImmutableList locationKeys =
+ ImmutableList.of(
+ AttributeKeys.GCE_CLOUD_REGION,
+ AttributeKeys.GCE_AVAILABILITY_ZONE,
+ AttributeKeys.GKE_LOCATION_TYPE_REGION,
+ AttributeKeys.GKE_CLUSTER_LOCATION);
+
+ String region =
+ locationKeys.stream().map(attrs::get).filter(Objects::nonNull).findFirst().orElse("global");
+
+ // Deal with possibility of a zone. Zones are of the form us-east1-c, but we want a region
+ // which, which is us-east1.
+ region = Splitter.on('-').splitToStream(region).limit(2).collect(Collectors.joining("-"));
+
+ String hostname = attrs.get(AttributeKeys.GCE_INSTANCE_NAME);
+ // TODO: add support for cloud run & gae by looking at SERVERLESS_COMPUTE_NAME & GAE_MODULE_NAME
+ if (hostname == null) {
+ hostname = detectHostname(envGetter, hostnameSupplier);
+ }
+
+ String hostId = Optional.ofNullable(attrs.get(AttributeKeys.GCE_INSTANCE_ID)).orElse("");
+
+ return builder()
+ .setPlatform(cloud_platform)
+ .setProject(detectedPlatform.getProjectId())
+ .setRegion(region)
+ .setHostId(hostId)
+ .setHostName(hostname)
+ .build();
+ }
+
+ private static String detectHostname(
+ Function envGetter, Supplier hostnameSupplier) {
+ String hostname = envGetter.apply("HOSTNAME");
+
+ if (hostname == null) {
+ try {
+ hostname = hostnameSupplier.get();
+ } catch (RuntimeException e) {
+ logger.log(Level.WARNING, "failed to detect hostname", e);
+ }
+ }
+ if (hostname == null) {
+ hostname = "";
+ }
+ return hostname;
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/attributes/MethodInfo.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/attributes/MethodInfo.java
new file mode 100644
index 0000000000..4312392afa
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/attributes/MethodInfo.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2025 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
+ *
+ * https://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.bigtable.data.v2.internal.csm.attributes;
+
+import com.google.auto.value.AutoValue;
+
+/** Method specific attributes. */
+@AutoValue
+public abstract class MethodInfo {
+
+ /** The name of the method. ie "Bigtable.ReadRow" */
+ public abstract String getName();
+
+ /** If the method is streaming (ie a scan). */
+ public abstract boolean getStreaming();
+
+ public static MethodInfo of(String name, boolean streaming) {
+ return builder().setName(name).setStreaming(streaming).build();
+ }
+
+ public static Builder builder() {
+ return new AutoValue_MethodInfo.Builder();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setName(String name);
+
+ public abstract Builder setStreaming(boolean isStreaming);
+
+ public abstract MethodInfo build();
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/attributes/Util.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/attributes/Util.java
new file mode 100644
index 0000000000..221452537d
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/attributes/Util.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2025 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
+ *
+ * https://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.bigtable.data.v2.internal.csm.attributes;
+
+import com.google.api.gax.grpc.GrpcStatusCode;
+import com.google.api.gax.rpc.ApiException;
+import com.google.bigtable.v2.AuthorizedViewName;
+import com.google.bigtable.v2.CheckAndMutateRowRequest;
+import com.google.bigtable.v2.GenerateInitialChangeStreamPartitionsRequest;
+import com.google.bigtable.v2.MaterializedViewName;
+import com.google.bigtable.v2.MutateRowRequest;
+import com.google.bigtable.v2.MutateRowsRequest;
+import com.google.bigtable.v2.PeerInfo;
+import com.google.bigtable.v2.PeerInfo.TransportType;
+import com.google.bigtable.v2.ReadChangeStreamRequest;
+import com.google.bigtable.v2.ReadModifyWriteRowRequest;
+import com.google.bigtable.v2.ReadRowsRequest;
+import com.google.bigtable.v2.ResponseParams;
+import com.google.bigtable.v2.SampleRowKeysRequest;
+import com.google.bigtable.v2.TableName;
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.Status;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.concurrent.CancellationException;
+import javax.annotation.Nullable;
+
+public class Util {
+ static final String TRANSPORT_TYPE_PREFIX = "TRANSPORT_TYPE_";
+
+ public static String formatTransportZone(@Nullable PeerInfo peerInfo) {
+ return Optional.ofNullable(peerInfo).map(PeerInfo::getApplicationFrontendZone).orElse("");
+ }
+
+ public static String formatTransportSubzone(@Nullable PeerInfo peerInfo) {
+ return Optional.ofNullable(peerInfo).map(PeerInfo::getApplicationFrontendSubzone).orElse("");
+ }
+
+ public static String formatTransportType(@Nullable PeerInfo peerInfo) {
+ return transportTypeToString(
+ Optional.ofNullable(peerInfo)
+ .map(PeerInfo::getTransportType)
+ .orElse(TransportType.TRANSPORT_TYPE_UNKNOWN));
+ }
+
+ public static String transportTypeToString(TransportType transportType) {
+ String label = transportTypeToStringWithoutFallback(transportType);
+ if (label != null) {
+ return label;
+ }
+ // In case the client is running with a newer version of protos
+ if (transportType.name().startsWith(TRANSPORT_TYPE_PREFIX)) {
+ return transportType
+ .name()
+ .substring(TRANSPORT_TYPE_PREFIX.length())
+ .toLowerCase(Locale.ENGLISH);
+ } else {
+ return transportType.name();
+ }
+ }
+
+ @VisibleForTesting
+ static String transportTypeToStringWithoutFallback(TransportType transportType) {
+ if (transportType == null) {
+ return "null";
+ }
+ switch (transportType) {
+ case TRANSPORT_TYPE_UNKNOWN:
+ return "unknown";
+ case TRANSPORT_TYPE_EXTERNAL:
+ return "external";
+ case TRANSPORT_TYPE_CLOUD_PATH:
+ return "cloudpath";
+ case TRANSPORT_TYPE_DIRECT_ACCESS:
+ return "directpath";
+ case TRANSPORT_TYPE_SESSION_UNKNOWN:
+ return "session_unknown";
+ case TRANSPORT_TYPE_SESSION_EXTERNAL:
+ return "session_external";
+ case TRANSPORT_TYPE_SESSION_CLOUD_PATH:
+ return "session_cloudpath";
+ case TRANSPORT_TYPE_SESSION_DIRECT_ACCESS:
+ return "session_directpath";
+ case UNRECOGNIZED:
+ return "unrecognized";
+ default:
+ return null;
+ }
+ }
+
+ public static String formatClusterIdMetricLabel(@Nullable ResponseParams clusterInfo) {
+ return Optional.ofNullable(clusterInfo)
+ .map(ResponseParams::getClusterId)
+ .filter(s -> !s.isEmpty())
+ .orElse("");
+ }
+
+ public static String formatZoneIdMetricLabel(@Nullable ResponseParams clusterInfo) {
+ return Optional.ofNullable(clusterInfo)
+ .map(ResponseParams::getZoneId)
+ .filter(s -> !s.isEmpty())
+ .orElse("global");
+ }
+
+ public static Status.Code extractStatus(@Nullable Throwable error) {
+ if (error == null) {
+ return Status.Code.OK;
+ }
+ // Handle java CancellationException as if it was a gax CancelledException
+ if (error instanceof CancellationException) {
+ return Status.Code.CANCELLED;
+ }
+ if (error instanceof ApiException) {
+ ApiException apiException = (ApiException) error;
+ if (apiException.getStatusCode() instanceof GrpcStatusCode) {
+ return ((GrpcStatusCode) apiException.getStatusCode()).getTransportCode();
+ }
+ }
+
+ Status s = Status.fromThrowable(error);
+ if (s != null) {
+ return s.getCode();
+ }
+ return Status.Code.UNKNOWN;
+ }
+
+ public static String extractTableId(Object request) {
+ String tableName = null;
+ String authorizedViewName = null;
+ String materializedViewName = null;
+ if (request instanceof ReadRowsRequest) {
+ tableName = ((ReadRowsRequest) request).getTableName();
+ authorizedViewName = ((ReadRowsRequest) request).getAuthorizedViewName();
+ materializedViewName = ((ReadRowsRequest) request).getMaterializedViewName();
+ } else if (request instanceof MutateRowsRequest) {
+ tableName = ((MutateRowsRequest) request).getTableName();
+ authorizedViewName = ((MutateRowsRequest) request).getAuthorizedViewName();
+ } else if (request instanceof MutateRowRequest) {
+ tableName = ((MutateRowRequest) request).getTableName();
+ authorizedViewName = ((MutateRowRequest) request).getAuthorizedViewName();
+ } else if (request instanceof SampleRowKeysRequest) {
+ tableName = ((SampleRowKeysRequest) request).getTableName();
+ authorizedViewName = ((SampleRowKeysRequest) request).getAuthorizedViewName();
+ materializedViewName = ((SampleRowKeysRequest) request).getMaterializedViewName();
+ } else if (request instanceof CheckAndMutateRowRequest) {
+ tableName = ((CheckAndMutateRowRequest) request).getTableName();
+ authorizedViewName = ((CheckAndMutateRowRequest) request).getAuthorizedViewName();
+ } else if (request instanceof ReadModifyWriteRowRequest) {
+ tableName = ((ReadModifyWriteRowRequest) request).getTableName();
+ authorizedViewName = ((ReadModifyWriteRowRequest) request).getAuthorizedViewName();
+ } else if (request instanceof GenerateInitialChangeStreamPartitionsRequest) {
+ tableName = ((GenerateInitialChangeStreamPartitionsRequest) request).getTableName();
+ } else if (request instanceof ReadChangeStreamRequest) {
+ tableName = ((ReadChangeStreamRequest) request).getTableName();
+ }
+ if (tableName != null && !tableName.isEmpty()) {
+ return TableName.parse(tableName).getTable();
+ }
+ if (authorizedViewName != null && !authorizedViewName.isEmpty()) {
+ return AuthorizedViewName.parse(authorizedViewName).getTable();
+ }
+ if (materializedViewName != null && !materializedViewName.isEmpty()) {
+ return MaterializedViewName.parse(materializedViewName).getMaterializedView();
+ }
+ return "";
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/exporter/BigtableCloudMonitoringExporter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/exporter/BigtableCloudMonitoringExporter.java
new file mode 100644
index 0000000000..2aa98c33ea
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/exporter/BigtableCloudMonitoringExporter.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2023 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
+ *
+ * https://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.bigtable.data.v2.internal.csm.exporter;
+
+import com.google.api.core.ApiFuture;
+import com.google.api.core.ApiFutureCallback;
+import com.google.api.core.ApiFutures;
+import com.google.api.gax.core.CredentialsProvider;
+import com.google.api.gax.core.FixedCredentialsProvider;
+import com.google.api.gax.core.NoCredentialsProvider;
+import com.google.api.gax.rpc.PermissionDeniedException;
+import com.google.auth.Credentials;
+import com.google.cloud.bigtable.data.v2.internal.csm.MetricRegistry;
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.EnvInfo;
+import com.google.cloud.monitoring.v3.MetricServiceClient;
+import com.google.cloud.monitoring.v3.MetricServiceSettings;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.monitoring.v3.CreateTimeSeriesRequest;
+import com.google.monitoring.v3.ProjectName;
+import com.google.monitoring.v3.TimeSeries;
+import com.google.protobuf.Empty;
+import io.opentelemetry.sdk.common.CompletableResultCode;
+import io.opentelemetry.sdk.metrics.InstrumentType;
+import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.opentelemetry.sdk.metrics.export.MetricExporter;
+import java.io.IOException;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+public class BigtableCloudMonitoringExporter implements MetricExporter {
+ private static final Logger LOGGER =
+ Logger.getLogger(BigtableCloudMonitoringExporter.class.getName());
+
+ // This system property can be used to override the monitoring endpoint
+ // to a different environment. It's meant for internal testing only and
+ // will be removed in future versions. Use settings in EnhancedBigtableStubSettings
+ // to override the endpoint.
+ @Deprecated @Nullable
+ private static final String MONITORING_ENDPOINT_OVERRIDE_SYS_PROP =
+ System.getProperty("bigtable.test-monitoring-endpoint");
+
+ // This the quota limit from Cloud Monitoring. More details in
+ // https://cloud.google.com/monitoring/quotas#custom_metrics_quotas.
+ private static final int EXPORT_BATCH_SIZE_LIMIT = 200;
+
+ private final Supplier envInfo;
+ private final ClientInfo clientInfo;
+ private final MetricRegistry metricRegistry;
+ private final MetricServiceClient client;
+
+ private final AtomicReference state;
+ private CompletableResultCode lastExportCode;
+ private final AtomicBoolean exportFailureLogged = new AtomicBoolean(false);
+
+ private enum State {
+ Running,
+ Closing,
+ Closed
+ }
+
+ public static BigtableCloudMonitoringExporter create(
+ MetricRegistry metricRegistry,
+ Supplier envInfo,
+ ClientInfo clientInfo,
+ @Nullable Credentials credentials,
+ @Nullable String endpoint,
+ String universeDomain)
+ throws IOException {
+
+ Preconditions.checkNotNull(universeDomain);
+
+ MetricServiceSettings.Builder settingsBuilder =
+ MetricServiceSettings.newBuilder()
+ .setUniverseDomain(universeDomain)
+ .setCredentialsProvider(
+ Optional.ofNullable(credentials)
+ .map(FixedCredentialsProvider::create)
+ .orElse(NoCredentialsProvider.create()));
+
+ if (MONITORING_ENDPOINT_OVERRIDE_SYS_PROP != null) {
+ LOGGER.warning(
+ "Setting the monitoring endpoint through system variable will be removed in future"
+ + " versions");
+ settingsBuilder.setEndpoint(MONITORING_ENDPOINT_OVERRIDE_SYS_PROP);
+ }
+
+ if (endpoint != null) {
+ settingsBuilder.setEndpoint(endpoint);
+ }
+
+ Duration timeout = Duration.ofMinutes(1);
+ // TODO: createServiceTimeSeries needs special handling if the request failed. Leaving
+ // it as not retried for now.
+ settingsBuilder.createServiceTimeSeriesSettings().setSimpleTimeoutNoRetriesDuration(timeout);
+
+ return new BigtableCloudMonitoringExporter(
+ metricRegistry, envInfo, clientInfo, MetricServiceClient.create(settingsBuilder.build()));
+ }
+
+ @VisibleForTesting
+ public BigtableCloudMonitoringExporter(
+ MetricRegistry metricRegistry,
+ Supplier envInfo,
+ ClientInfo clientInfo,
+ MetricServiceClient client) {
+ this.metricRegistry = metricRegistry;
+ this.envInfo = envInfo;
+ this.clientInfo = clientInfo;
+ this.client = client;
+ this.state = new AtomicReference<>(State.Running);
+ }
+
+ public void close() {
+ client.close();
+ }
+
+ @Override
+ public CompletableResultCode export(Collection metricData) {
+ Preconditions.checkState(state.get() != State.Closed, "Exporter is closed");
+
+ lastExportCode = doExport(metricData);
+ return lastExportCode;
+ }
+
+ private CompletableResultCode doExport(Collection metricData) {
+ Map> converted;
+
+ try {
+ converted = new Converter(metricRegistry, envInfo.get(), clientInfo).convertAll(metricData);
+ } catch (Throwable t) {
+ if (exportFailureLogged.compareAndSet(false, true)) {
+ LOGGER.log(Level.WARNING, "Failed to compose metrics for export", t);
+ }
+
+ return CompletableResultCode.ofExceptionalFailure(t);
+ }
+
+ List> futures = new ArrayList<>();
+
+ for (Entry> e : converted.entrySet()) {
+ futures.addAll(exportTimeSeries(e.getKey(), e.getValue()));
+ }
+
+ CompletableResultCode exportCode = new CompletableResultCode();
+
+ StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+
+ ApiFutures.addCallback(
+ ApiFutures.allAsList(futures),
+ new ApiFutureCallback>() {
+ @Override
+ public void onFailure(Throwable throwable) {
+ if (exportFailureLogged.compareAndSet(false, true)) {
+ String msg = "createServiceTimeSeries request failed";
+ if (throwable instanceof PermissionDeniedException) {
+ msg +=
+ String.format(
+ " Need monitoring metric writer permission on project=%s. Follow"
+ + " https://cloud.google.com/bigtable/docs/client-side-metrics-setup"
+ + " to set up permissions.",
+ clientInfo.getInstanceName().getProjectId());
+ }
+ RuntimeException asyncWrapper = new RuntimeException("export failed", throwable);
+ asyncWrapper.setStackTrace(stackTrace);
+
+ if (state.get() != State.Closing || state.get() != State.Closed) {
+ // ignore the export warning when client is shutting down
+ LOGGER.log(Level.WARNING, msg, asyncWrapper);
+ }
+ }
+ exportCode.fail();
+ }
+
+ @Override
+ public void onSuccess(List objects) {
+ exportFailureLogged.set(false);
+ exportCode.succeed();
+ }
+ },
+ MoreExecutors.directExecutor());
+ return exportCode;
+ }
+
+ private List> exportTimeSeries(
+ ProjectName projectName, Collection timeSeries) {
+ List> batchResults = new ArrayList<>();
+
+ for (List batch : Iterables.partition(timeSeries, EXPORT_BATCH_SIZE_LIMIT)) {
+ CreateTimeSeriesRequest req =
+ CreateTimeSeriesRequest.newBuilder()
+ .setName(projectName.toString())
+ .addAllTimeSeries(batch)
+ .build();
+ ApiFuture f = this.client.createServiceTimeSeriesCallable().futureCall(req);
+ batchResults.add(f);
+ }
+
+ return batchResults;
+ }
+
+ @Override
+ public CompletableResultCode flush() {
+ if (lastExportCode != null) {
+ return lastExportCode;
+ }
+ return CompletableResultCode.ofSuccess();
+ }
+
+ @Override
+ public CompletableResultCode shutdown() {
+ State prevState = state.getAndSet(State.Closed);
+ if (prevState == State.Closed) {
+ LOGGER.log(Level.WARNING, "shutdown is called multiple times");
+ return CompletableResultCode.ofSuccess();
+ }
+ CompletableResultCode flushResult = flush();
+ CompletableResultCode shutdownResult = new CompletableResultCode();
+ flushResult.whenComplete(
+ () -> {
+ Throwable throwable = null;
+ try {
+ client.shutdown();
+ } catch (Throwable e) {
+ LOGGER.log(Level.WARNING, "failed to shutdown the monitoring client", e);
+ throwable = e;
+ }
+ if (throwable != null) {
+ shutdownResult.fail();
+ } else {
+ shutdownResult.succeed();
+ }
+ });
+
+ return CompletableResultCode.ofAll(Arrays.asList(flushResult, shutdownResult));
+ }
+
+ @Override
+ public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
+ return AggregationTemporality.CUMULATIVE;
+ }
+
+ public void prepareForShutdown() {
+ state.compareAndSet(State.Running, State.Closing);
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/exporter/BigtablePeriodicReader.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/exporter/BigtablePeriodicReader.java
new file mode 100644
index 0000000000..d29dbd8702
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/exporter/BigtablePeriodicReader.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2026 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
+ *
+ * https://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.bigtable.data.v2.internal.csm.exporter;
+
+import io.opentelemetry.sdk.common.CompletableResultCode;
+import io.opentelemetry.sdk.common.export.MemoryMode;
+import io.opentelemetry.sdk.metrics.Aggregation;
+import io.opentelemetry.sdk.metrics.InstrumentType;
+import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
+import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector;
+import io.opentelemetry.sdk.metrics.export.CollectionRegistration;
+import io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector;
+import io.opentelemetry.sdk.metrics.export.MetricReader;
+import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
+import java.io.IOException;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * Wrapper around a {@link PeriodicMetricReader} that will notify the exporter when it's shutting
+ * down. This is necessary to filter out noisy error logs on shutdown.
+ */
+public class BigtablePeriodicReader implements MetricReader {
+ private final MetricReader delegate;
+ private final BigtableCloudMonitoringExporter exporter;
+
+ public BigtablePeriodicReader(
+ BigtableCloudMonitoringExporter exporter, ScheduledExecutorService executor) {
+ delegate = PeriodicMetricReader.builder(exporter).setExecutor(executor).build();
+ this.exporter = exporter;
+ }
+
+ @Override
+ public void register(CollectionRegistration registration) {
+ delegate.register(registration);
+ }
+
+ @Override
+ public Aggregation getDefaultAggregation(InstrumentType instrumentType) {
+ return delegate.getDefaultAggregation(instrumentType);
+ }
+
+ @Override
+ public MemoryMode getMemoryMode() {
+ return delegate.getMemoryMode();
+ }
+
+ @Override
+ public CompletableResultCode forceFlush() {
+ return delegate.forceFlush();
+ }
+
+ @Override
+ public CompletableResultCode shutdown() {
+ return delegate.shutdown();
+ }
+
+ @Override
+ public void close() throws IOException {
+ exporter.prepareForShutdown();
+ delegate.close();
+ }
+
+ public static AggregationTemporalitySelector alwaysCumulative() {
+ return AggregationTemporalitySelector.alwaysCumulative();
+ }
+
+ public static AggregationTemporalitySelector deltaPreferred() {
+ return AggregationTemporalitySelector.deltaPreferred();
+ }
+
+ public static AggregationTemporalitySelector lowMemory() {
+ return AggregationTemporalitySelector.lowMemory();
+ }
+
+ @Override
+ public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
+ return delegate.getAggregationTemporality(instrumentType);
+ }
+
+ public static String asString(AggregationTemporalitySelector selector) {
+ return AggregationTemporalitySelector.asString(selector);
+ }
+
+ public static DefaultAggregationSelector getDefault() {
+ return DefaultAggregationSelector.getDefault();
+ }
+
+ @Override
+ public DefaultAggregationSelector with(InstrumentType instrumentType, Aggregation aggregation) {
+ return delegate.with(instrumentType, aggregation);
+ }
+
+ public static String asString(DefaultAggregationSelector selector) {
+ return DefaultAggregationSelector.asString(selector);
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/exporter/Converter.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/exporter/Converter.java
new file mode 100644
index 0000000000..c5ec4b3332
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/exporter/Converter.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2025 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
+ *
+ * https://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.bigtable.data.v2.internal.csm.exporter;
+
+import static com.google.api.MetricDescriptor.MetricKind.CUMULATIVE;
+import static com.google.api.MetricDescriptor.MetricKind.GAUGE;
+import static com.google.api.MetricDescriptor.MetricKind.UNRECOGNIZED;
+import static com.google.api.MetricDescriptor.ValueType.DISTRIBUTION;
+import static com.google.api.MetricDescriptor.ValueType.DOUBLE;
+import static com.google.api.MetricDescriptor.ValueType.INT64;
+
+import com.google.api.Distribution;
+import com.google.api.Distribution.BucketOptions;
+import com.google.api.Distribution.BucketOptions.Explicit;
+import com.google.api.Metric;
+import com.google.api.MetricDescriptor.MetricKind;
+import com.google.api.MetricDescriptor.ValueType;
+import com.google.cloud.bigtable.data.v2.internal.csm.MetricRegistry;
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.EnvInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.MetricWrapper;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
+import com.google.monitoring.v3.Point;
+import com.google.monitoring.v3.ProjectName;
+import com.google.monitoring.v3.TimeInterval;
+import com.google.monitoring.v3.TimeSeries;
+import com.google.monitoring.v3.TypedValue;
+import com.google.protobuf.util.Timestamps;
+import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
+import io.opentelemetry.sdk.metrics.data.DoublePointData;
+import io.opentelemetry.sdk.metrics.data.HistogramData;
+import io.opentelemetry.sdk.metrics.data.HistogramPointData;
+import io.opentelemetry.sdk.metrics.data.LongPointData;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.opentelemetry.sdk.metrics.data.MetricDataType;
+import io.opentelemetry.sdk.metrics.data.PointData;
+import io.opentelemetry.sdk.metrics.data.SumData;
+import java.util.Collection;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Helper for exporting metrics from Opentelemetry to Cloud Monitoring.
+ *
+ * Takes collection {@link MetricData} and uses the {@link MetricWrapper}s defined in {@link
+ * MetricRegistry} to compose both the {@link com.google.api.MonitoredResource} and {@link Point}.
+ */
+class Converter {
+ private static final Logger LOGGER = Logger.getLogger(Converter.class.getName());
+
+ private final MetricRegistry metricRegistry;
+ private final EnvInfo envInfo;
+ private final ClientInfo clientInfo;
+
+ Converter(MetricRegistry metricRegistry, EnvInfo envInfo, ClientInfo clientInfo) {
+ this.metricRegistry = metricRegistry;
+ this.envInfo = envInfo;
+ this.clientInfo = clientInfo;
+ }
+
+ Map> convertAll(Collection otelMetrics) {
+ ImmutableMultimap.Builder builder = ImmutableMultimap.builder();
+
+ for (MetricData metricData : otelMetrics) {
+ Multimap perProject = convertMetricData(metricData);
+ builder.putAll(perProject);
+ }
+ return builder.build().asMap();
+ }
+
+ private Multimap convertMetricData(MetricData metricData) {
+ MetricWrapper> metricDef = metricRegistry.getMetric(metricData.getName());
+ if (metricDef == null) {
+ LOGGER.log(Level.FINE, "Skipping unexpected metric: {}", metricData.getName());
+ return ImmutableListMultimap.of();
+ }
+
+ ImmutableMultimap.Builder builder = ImmutableMultimap.builder();
+
+ for (PointData pd : metricData.getData().getPoints()) {
+ ProjectName projectName =
+ metricDef.getSchema().extractProjectName(pd.getAttributes(), envInfo, clientInfo);
+
+ TimeSeries timeSeries =
+ TimeSeries.newBuilder()
+ .setMetricKind(convertMetricKind(metricData))
+ .setValueType(convertValueType(metricData.getType()))
+ .setResource(
+ metricDef
+ .getSchema()
+ .extractMonitoredResource(pd.getAttributes(), envInfo, clientInfo))
+ .setMetric(
+ Metric.newBuilder()
+ .setType(metricDef.getExternalName())
+ .putAllLabels(
+ metricDef.extractMetricLabels(pd.getAttributes(), envInfo, clientInfo)))
+ .addPoints(convertPointData(metricData.getType(), pd))
+ .build();
+
+ builder.put(projectName, timeSeries);
+ }
+ return builder.build();
+ }
+
+ private Point convertPointData(MetricDataType type, PointData pointData) {
+ TimeInterval timeInterval =
+ TimeInterval.newBuilder()
+ .setStartTime(Timestamps.fromNanos(pointData.getStartEpochNanos()))
+ .setEndTime(Timestamps.fromNanos(pointData.getEpochNanos()))
+ .build();
+
+ Point.Builder builder = Point.newBuilder().setInterval(timeInterval);
+ switch (type) {
+ case HISTOGRAM:
+ case EXPONENTIAL_HISTOGRAM:
+ return builder
+ .setValue(
+ TypedValue.newBuilder()
+ .setDistributionValue(convertHistogramData((HistogramPointData) pointData))
+ .build())
+ .build();
+ case DOUBLE_GAUGE:
+ case DOUBLE_SUM:
+ return builder
+ .setValue(
+ TypedValue.newBuilder()
+ .setDoubleValue(((DoublePointData) pointData).getValue())
+ .build())
+ .build();
+ case LONG_GAUGE:
+ case LONG_SUM:
+ return builder
+ .setValue(TypedValue.newBuilder().setInt64Value(((LongPointData) pointData).getValue()))
+ .build();
+ default:
+ LOGGER.log(Level.WARNING, "unsupported metric type %s", type);
+ return builder.build();
+ }
+ }
+
+ private static Distribution convertHistogramData(HistogramPointData pointData) {
+ return Distribution.newBuilder()
+ .setCount(pointData.getCount())
+ .setMean(pointData.getCount() == 0L ? 0.0D : pointData.getSum() / pointData.getCount())
+ .setBucketOptions(
+ BucketOptions.newBuilder()
+ .setExplicitBuckets(Explicit.newBuilder().addAllBounds(pointData.getBoundaries())))
+ .addAllBucketCounts(pointData.getCounts())
+ .build();
+ }
+
+ private static MetricKind convertMetricKind(MetricData metricData) {
+ switch (metricData.getType()) {
+ case HISTOGRAM:
+ case EXPONENTIAL_HISTOGRAM:
+ return convertHistogramType(metricData.getHistogramData());
+ case LONG_GAUGE:
+ case DOUBLE_GAUGE:
+ return GAUGE;
+ case LONG_SUM:
+ return convertSumDataType(metricData.getLongSumData());
+ case DOUBLE_SUM:
+ return convertSumDataType(metricData.getDoubleSumData());
+ default:
+ return UNRECOGNIZED;
+ }
+ }
+
+ private static MetricKind convertHistogramType(HistogramData histogramData) {
+ if (histogramData.getAggregationTemporality() == AggregationTemporality.CUMULATIVE) {
+ return CUMULATIVE;
+ }
+ return UNRECOGNIZED;
+ }
+
+ private static MetricKind convertSumDataType(SumData> sum) {
+ if (!sum.isMonotonic()) {
+ return GAUGE;
+ }
+ if (sum.getAggregationTemporality() == AggregationTemporality.CUMULATIVE) {
+ return CUMULATIVE;
+ }
+ return UNRECOGNIZED;
+ }
+
+ private static ValueType convertValueType(MetricDataType metricDataType) {
+ switch (metricDataType) {
+ case LONG_GAUGE:
+ case LONG_SUM:
+ return INT64;
+ case DOUBLE_GAUGE:
+ case DOUBLE_SUM:
+ return DOUBLE;
+ case HISTOGRAM:
+ case EXPONENTIAL_HISTOGRAM:
+ return DISTRIBUTION;
+ default:
+ return ValueType.UNRECOGNIZED;
+ }
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientBatchWriteFlowControlFactor.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientBatchWriteFlowControlFactor.java
new file mode 100644
index 0000000000..c4c6d97118
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientBatchWriteFlowControlFactor.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2026 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
+ *
+ * https://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.bigtable.data.v2.internal.csm.metrics;
+
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.MethodInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.Constants.MetricLabels;
+import com.google.cloud.bigtable.data.v2.internal.csm.schema.ClientSchema;
+import io.grpc.Status;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.DoubleGauge;
+import io.opentelemetry.api.metrics.Meter;
+
+public class ClientBatchWriteFlowControlFactor extends MetricWrapper {
+ public static final String NAME =
+ "bigtable.googleapis.com/internal/client/batch_write_flow_control_factor";
+
+ public ClientBatchWriteFlowControlFactor() {
+ super(ClientSchema.INSTANCE, NAME);
+ }
+
+ public Recorder newRecorder(Meter meter) {
+ return new Recorder(meter);
+ }
+
+ public class Recorder {
+ private final DoubleGauge instrument;
+
+ private Recorder(Meter meter) {
+ this.instrument =
+ meter
+ .gaugeBuilder(NAME)
+ .setDescription(
+ "The distribution of batch write flow control factors received from the server.")
+ .setUnit("1")
+ .build();
+ }
+
+ public void record(
+ ClientInfo clientInfo,
+ Status.Code code,
+ boolean applied,
+ MethodInfo methodInfo,
+ double factor) {
+ Attributes attributes =
+ getSchema()
+ .createResourceAttrs(clientInfo)
+ .put(MetricLabels.STATUS_KEY, code.name())
+ .put(MetricLabels.APPLIED_KEY, applied)
+ .put(MetricLabels.METHOD_KEY, methodInfo.getName())
+ .build();
+
+ instrument.set(factor, attributes);
+ }
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientBatchWriteFlowControlTargetQps.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientBatchWriteFlowControlTargetQps.java
new file mode 100644
index 0000000000..a15189aa4a
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientBatchWriteFlowControlTargetQps.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2026 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
+ *
+ * https://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.bigtable.data.v2.internal.csm.metrics;
+
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.MethodInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.Constants.MetricLabels;
+import com.google.cloud.bigtable.data.v2.internal.csm.schema.ClientSchema;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.DoubleGauge;
+import io.opentelemetry.api.metrics.Meter;
+
+public class ClientBatchWriteFlowControlTargetQps extends MetricWrapper {
+ public static final String NAME =
+ "bigtable.googleapis.com/internal/client/batch_write_flow_control_target_qps";
+
+ public ClientBatchWriteFlowControlTargetQps() {
+ super(ClientSchema.INSTANCE, NAME);
+ }
+
+ public Recorder newRecorder(Meter meter) {
+ return new Recorder(meter);
+ }
+
+ public class Recorder {
+ private final DoubleGauge instrument;
+
+ private Recorder(Meter meter) {
+ this.instrument =
+ meter
+ .gaugeBuilder(NAME)
+ .setDescription(
+ "The current target QPS of the client under batch write flow control.")
+ .setUnit("1")
+ .build();
+ }
+
+ public void record(ClientInfo clientInfo, MethodInfo methodInfo, double qps) {
+ Attributes attributes =
+ getSchema()
+ .createResourceAttrs(clientInfo)
+ .put(MetricLabels.METHOD_KEY, methodInfo.getName())
+ .build();
+
+ instrument.set(qps, attributes);
+ }
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientChannelPoolOutstandingRpcs.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientChannelPoolOutstandingRpcs.java
new file mode 100644
index 0000000000..c5c1589c4f
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientChannelPoolOutstandingRpcs.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2026 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
+ *
+ * https://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.bigtable.data.v2.internal.csm.metrics;
+
+import com.google.bigtable.v2.PeerInfo.TransportType;
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.Util;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.Constants.Buckets;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.Constants.MetricLabels;
+import com.google.cloud.bigtable.data.v2.internal.csm.schema.ClientSchema;
+import com.google.cloud.bigtable.gaxx.grpc.BigtableChannelPoolSettings.LoadBalancingStrategy;
+import io.opentelemetry.api.metrics.LongHistogram;
+import io.opentelemetry.api.metrics.Meter;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ClientChannelPoolOutstandingRpcs extends MetricWrapper {
+ public static final String NAME =
+ "bigtable.googleapis.com/internal/client/connection_pool/outstanding_rpcs";
+
+ private static final List BUCKETS =
+ Buckets.generateLinearSeq(0d, 200d, 5).stream()
+ .map(Double::longValue)
+ .collect(Collectors.toList());
+
+ public ClientChannelPoolOutstandingRpcs() {
+ super(ClientSchema.INSTANCE, NAME);
+ }
+
+ public Recorder newRecorder(Meter meter) {
+ return new Recorder(meter);
+ }
+
+ public class Recorder {
+ private final LongHistogram instrument;
+
+ private Recorder(Meter meter) {
+ this.instrument =
+ meter
+ .histogramBuilder(NAME)
+ .ofLongs()
+ .setExplicitBucketBoundariesAdvice(BUCKETS)
+ .setDescription(
+ "A distribution of the number of outstanding RPCs per connection in the client"
+ + " pool, sampled periodically.")
+ .setUnit("1")
+ .build();
+ }
+
+ public void record(
+ ClientInfo clientInfo,
+ TransportType transportType,
+ LoadBalancingStrategy lbPolicy,
+ boolean isStreaming,
+ long rpcCount) {
+ instrument.record(
+ rpcCount,
+ getSchema()
+ .createResourceAttrs(clientInfo)
+ .put(MetricLabels.TRANSPORT_TYPE, Util.transportTypeToString(transportType))
+ .put(MetricLabels.CHANNEL_POOL_LB_POLICY, lbPolicy.name())
+ .put(MetricLabels.STREAMING_KEY, isStreaming)
+ .build());
+ }
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientDpCompatGuage.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientDpCompatGuage.java
new file mode 100644
index 0000000000..9746e67448
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientDpCompatGuage.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2026 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
+ *
+ * https://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.bigtable.data.v2.internal.csm.metrics;
+
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.Constants.MetricLabels;
+import com.google.cloud.bigtable.data.v2.internal.csm.schema.ClientSchema;
+import io.opentelemetry.api.metrics.LongGauge;
+import io.opentelemetry.api.metrics.Meter;
+
+public class ClientDpCompatGuage extends MetricWrapper {
+ private static final String NAME =
+ "bigtable.googleapis.com/internal/client/direct_access/compatible";
+
+ public ClientDpCompatGuage() {
+ super(ClientSchema.INSTANCE, NAME);
+ }
+
+ public Recorder newRecorder(Meter meter) {
+ return new Recorder(meter);
+ }
+
+ public class Recorder {
+ private final LongGauge instrument;
+
+ private Recorder(Meter meter) {
+ this.instrument =
+ meter
+ .gaugeBuilder(NAME)
+ .ofLongs()
+ .setDescription(
+ "Reports 1 if the environment is eligible for DirectPath, 0 otherwise. Based on"
+ + " an attempt at startup.")
+ .setUnit("1")
+ .build();
+ }
+
+ // TODO: replace ipPreference with an enum
+ public void recordSuccess(ClientInfo clientInfo, String ipPreference) {
+ instrument.set(
+ 1,
+ getSchema()
+ .createResourceAttrs(clientInfo)
+ .put(MetricLabels.DP_REASON_KEY, "")
+ .put(MetricLabels.DP_IP_PREFERENCE_KEY, ipPreference)
+ .build());
+ }
+
+ // TODO: replace reason with an enum
+ public void recordFailure(ClientInfo clientInfo, String reason) {
+ instrument.set(
+ 1,
+ getSchema()
+ .createResourceAttrs(clientInfo)
+ .put(MetricLabels.DP_REASON_KEY, reason)
+ .put(MetricLabels.DP_IP_PREFERENCE_KEY, "")
+ .build());
+ }
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientPerConnectionErrorCount.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientPerConnectionErrorCount.java
new file mode 100644
index 0000000000..dc07f6e0e9
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/ClientPerConnectionErrorCount.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2025 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
+ *
+ * https://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.bigtable.data.v2.internal.csm.metrics;
+
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.ClientInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.attributes.EnvInfo;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.Constants.Buckets;
+import com.google.cloud.bigtable.data.v2.internal.csm.metrics.Constants.MetricLabels;
+import com.google.cloud.bigtable.data.v2.internal.csm.schema.ClientSchema;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.LongHistogram;
+import io.opentelemetry.api.metrics.Meter;
+import java.util.List;
+import java.util.Set;
+
+public class ClientPerConnectionErrorCount extends MetricWrapper {
+ public static final String NAME =
+ "bigtable.googleapis.com/internal/client/per_connection_error_count";
+
+ static final List BUCKETS =
+ ImmutableList.builder()
+ .add(0L)
+ .addAll(Buckets.generateGeometricSeq(1, 64))
+ .addAll(Buckets.generateGeometricSeq(125, 1_000_000L))
+ .build();
+ // This metric migrated from gce/gke schemas to bigtable_client
+ // So a lot of the metric labels overlap with the resource labels.
+ // we need special handling since the logic in MetricWrapper assumes that there is no
+ // overlap.
+ private static final Set> METRIC_LABELS =
+ ImmutableSet.of(
+ MetricLabels.BIGTABLE_PROJECT_ID_KEY,
+ MetricLabels.CLIENT_UID,
+ MetricLabels.INSTANCE_ID_KEY,
+ MetricLabels.CLIENT_NAME,
+ MetricLabels.APP_PROFILE_KEY);
+
+ public ClientPerConnectionErrorCount() {
+ super(ClientSchema.INSTANCE, NAME);
+ }
+
+ // Override the default metric labels to account for backwards compatibility.
+ // This metric used to live under bigtable_table, and has moved to bigtable_client
+ // The new schema duplicates some of the metric labels. However the default implementation
+ // in MetricWrapper will remove all resource labels from the metric labels.
+ // To maintain backwards compatibility, this metric override the extractMetricLabels
+ // to always emit the duplicate metric labels.
+ @Override
+ public ImmutableMap extractMetricLabels(
+ Attributes metricAttrs, EnvInfo envInfo, ClientInfo clientInfo) {
+ ImmutableMap.Builder builder = ImmutableMap.builder();
+ metricAttrs.forEach(
+ (k, v) -> {
+ if (METRIC_LABELS.contains(k) && v != null) {
+ builder.put(k.getKey(), v.toString());
+ }
+ });
+ builder.put(MetricLabels.CLIENT_UID.getKey(), envInfo.getUid());
+ return builder.build();
+ }
+
+ public Recorder newRecorder(Meter meter) {
+ return new Recorder(meter);
+ }
+
+ public class Recorder {
+ private final LongHistogram instrument;
+
+ private Recorder(Meter meter) {
+ instrument =
+ meter
+ .histogramBuilder(NAME)
+ .ofLongs()
+ .setDescription("Distribution of counts of channels per 'error count per minute'.")
+ .setUnit("1")
+ .setExplicitBucketBoundariesAdvice(BUCKETS)
+ .build();
+ }
+
+ public void record(ClientInfo clientInfo, long value) {
+ Attributes attributes =
+ getSchema()
+ .createResourceAttrs(clientInfo)
+ .put(
+ MetricLabels.BIGTABLE_PROJECT_ID_KEY, clientInfo.getInstanceName().getProjectId())
+ .put(MetricLabels.INSTANCE_ID_KEY, clientInfo.getInstanceName().getInstanceId())
+ .put(MetricLabels.CLIENT_NAME, clientInfo.getClientName())
+ .put(MetricLabels.APP_PROFILE_KEY, clientInfo.getAppProfileId())
+ .build();
+ instrument.record(value, attributes);
+ }
+ }
+}
diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/Constants.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/Constants.java
new file mode 100644
index 0000000000..3478fd2e42
--- /dev/null
+++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/internal/csm/metrics/Constants.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2025 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
+ *
+ * https://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.bigtable.data.v2.internal.csm.metrics;
+
+import com.google.common.collect.ImmutableList;
+import io.opentelemetry.api.common.AttributeKey;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class Constants {
+ private Constants() {}
+
+ public static final class MetricLabels {
+ private MetricLabels() {}
+
+ // TODO: remove overlapping attributes
+ // Project & Instance overlap with resource labels because they were migrated from
+ // an old gce/gke schema to support per_connection_error_count metric
+ @Deprecated
+ public static final AttributeKey BIGTABLE_PROJECT_ID_KEY =
+ AttributeKey.stringKey("project_id");
+
+ @Deprecated
+ public static final AttributeKey INSTANCE_ID_KEY = AttributeKey.stringKey("instance");
+
+ public static final AttributeKey TRANSPORT_TYPE =
+ AttributeKey.stringKey("transport_type");
+ public static final AttributeKey TRANSPORT_REGION =
+ AttributeKey.stringKey("transport_region");
+ public static final AttributeKey TRANSPORT_ZONE =
+ AttributeKey.stringKey("transport_zone");
+ public static final AttributeKey TRANSPORT_SUBZONE =
+ AttributeKey.stringKey("transport_subzone");
+
+ public static final AttributeKey CLIENT_UID = AttributeKey.stringKey("client_uid");
+ public static final AttributeKey CLIENT_NAME = AttributeKey.stringKey("client_name");
+ public static final AttributeKey METHOD_KEY = AttributeKey.stringKey("method");
+ public static final AttributeKey STREAMING_KEY = AttributeKey.booleanKey("streaming");
+ public static final AttributeKey APP_PROFILE_KEY =
+ AttributeKey.stringKey("app_profile");
+ public static final AttributeKey DEBUG_TAG_KEY = AttributeKey.stringKey("tag");
+
+ public static final AttributeKey APPLIED_KEY = AttributeKey.booleanKey("applied");
+
+ static final AttributeKey CHANNEL_POOL_LB_POLICY = AttributeKey.stringKey("lb_policy");
+ static final AttributeKey