-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(bigquery-jdbc): add opt-out controls #14199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4cca913
3d5c91e
4f45388
3de37e2
ffcc65d
bdffd35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,12 +17,12 @@ | |
| package com.google.cloud.bigquery.jdbc.telemetry.v1; | ||
|
|
||
| import java.util.Objects; | ||
| import java.util.Properties; | ||
|
|
||
| /** Configuration settings for the BigQuery JDBC driver telemetry client. */ | ||
| final class TelemetryConfiguration { | ||
| static final boolean DEFAULT_ENABLED = true; | ||
| // TODO: change DEFAULT_LOG_SOURCE value once the value is assigned. | ||
| static final int DEFAULT_LOG_SOURCE = -1; | ||
| static final int DEFAULT_LOG_SOURCE = 3071; | ||
| static final String DEFAULT_ENDPOINT_URL = "https://play.googleapis.com/log"; | ||
| static final long DEFAULT_UPLOAD_INTERVAL_MS = 300_000L; | ||
| static final int DEFAULT_BATCH_SIZE_THRESHOLD = 5000; | ||
|
|
@@ -156,6 +156,93 @@ Builder setDriverEnvironment(DriverEnvironment driverEnvironment) { | |
| return this; | ||
| } | ||
|
|
||
| Builder resolveProperties(Properties connectionProperties) { | ||
| // 1. Connection Properties (lowest precedence) | ||
| if (connectionProperties != null) { | ||
| String propValue = connectionProperties.getProperty("EnableDiagnosticTelemetry"); | ||
| if (propValue == null) { | ||
| propValue = connectionProperties.getProperty("enableDiagnosticTelemetry"); | ||
| } | ||
| if (propValue != null) { | ||
| if ("0".equals(propValue) || "false".equalsIgnoreCase(propValue)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have |
||
| this.enabled = false; | ||
| } else if ("1".equals(propValue) || "true".equalsIgnoreCase(propValue)) { | ||
| this.enabled = true; | ||
| } | ||
| } | ||
|
|
||
| String uploadIntervalStr = connectionProperties.getProperty("TelemetryUploadInterval"); | ||
| if (uploadIntervalStr != null) { | ||
| try { | ||
| this.uploadIntervalMs = Long.parseLong(uploadIntervalStr); | ||
| } catch (NumberFormatException ignored) { | ||
| } | ||
| } | ||
|
|
||
| String batchSizeStr = connectionProperties.getProperty("TelemetryBatchSize"); | ||
| if (batchSizeStr != null) { | ||
| try { | ||
| this.batchSizeThreshold = Integer.parseInt(batchSizeStr); | ||
| } catch (NumberFormatException ignored) { | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // 2. Environment Variables (overrides connection properties) | ||
| String envEnabled = System.getenv("GOOGLE_CLOUD_TELEMETRY_ENABLED"); | ||
| if (envEnabled != null) { | ||
| if ("0".equals(envEnabled) || "false".equalsIgnoreCase(envEnabled)) { | ||
| this.enabled = false; | ||
| } else if ("1".equals(envEnabled) || "true".equalsIgnoreCase(envEnabled)) { | ||
| this.enabled = true; | ||
| } | ||
| } | ||
|
|
||
| String envInterval = System.getenv("GOOGLE_CLOUD_TELEMETRY_UPLOAD_INTERVAL"); | ||
| if (envInterval != null) { | ||
| try { | ||
| this.uploadIntervalMs = Long.parseLong(envInterval); | ||
| } catch (NumberFormatException ignored) { | ||
| } | ||
| } | ||
|
|
||
| String envBatch = System.getenv("GOOGLE_CLOUD_TELEMETRY_BATCH_SIZE"); | ||
| if (envBatch != null) { | ||
| try { | ||
| this.batchSizeThreshold = Integer.parseInt(envBatch); | ||
| } catch (NumberFormatException ignored) { | ||
| } | ||
| } | ||
|
|
||
| // 3. JVM System Properties (highest precedence) | ||
| String sysEnabled = System.getProperty("GOOGLE_CLOUD_TELEMETRY_ENABLED"); | ||
| if (sysEnabled != null) { | ||
| if ("0".equals(sysEnabled) || "false".equalsIgnoreCase(sysEnabled)) { | ||
| this.enabled = false; | ||
| } else if ("1".equals(sysEnabled) || "true".equalsIgnoreCase(sysEnabled)) { | ||
| this.enabled = true; | ||
| } | ||
| } | ||
|
|
||
| String sysInterval = System.getProperty("GOOGLE_CLOUD_TELEMETRY_UPLOAD_INTERVAL"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: we have 2 types here, int & bool. I'd suggest moving it to helper methods to have smth like this (Second param for default value) |
||
| if (sysInterval != null) { | ||
| try { | ||
| this.uploadIntervalMs = Long.parseLong(sysInterval); | ||
| } catch (NumberFormatException ignored) { | ||
| } | ||
| } | ||
|
|
||
| String sysBatch = System.getProperty("GOOGLE_CLOUD_TELEMETRY_BATCH_SIZE"); | ||
| if (sysBatch != null) { | ||
| try { | ||
| this.batchSizeThreshold = Integer.parseInt(sysBatch); | ||
| } catch (NumberFormatException ignored) { | ||
| } | ||
| } | ||
|
|
||
| return this; | ||
| } | ||
|
|
||
| TelemetryConfiguration build() { | ||
| return new TelemetryConfiguration(this); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,10 @@ | |
|
|
||
| package com.google.cloud.bigquery.jdbc.telemetry.v1; | ||
|
|
||
| import com.google.cloud.bigquery.JobStatistics.QueryStatistics; | ||
| import com.google.cloud.bigquery.jdbc.BigQueryJdbcCustomLogger; | ||
| import com.google.protobuf.Descriptors.EnumValueDescriptor; | ||
| import java.util.Properties; | ||
| import java.util.logging.Level; | ||
| import java.util.logging.Logger; | ||
|
|
||
|
|
@@ -32,6 +35,7 @@ final class TelemetryManager implements AutoCloseable { | |
| new BigQueryJdbcCustomLogger(TelemetryManager.class.getName()); | ||
|
|
||
| private static volatile TelemetryManager instance; | ||
| private static volatile boolean globallyDisabled = false; | ||
|
|
||
| private final TelemetryBatcher batcher; | ||
|
|
||
|
|
@@ -44,12 +48,36 @@ private TelemetryManager(TelemetryBatcher batcher) { | |
| * and transport. | ||
| */ | ||
| static TelemetryManager getInstance() { | ||
| return getInstance(null); | ||
| } | ||
|
|
||
| static TelemetryManager getInstance(Properties properties) { | ||
| if (globallyDisabled) { | ||
| return null; | ||
| } | ||
|
|
||
| if (properties != null) { | ||
| TelemetryConfiguration configCheck = | ||
| TelemetryConfiguration.builder().resolveProperties(properties).build(); | ||
| if (!configCheck.isEnabled()) { | ||
| synchronized (TelemetryManager.class) { | ||
| globallyDisabled = true; | ||
| closeInstance(); | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| TelemetryManager localRef = instance; | ||
| if (localRef == null) { | ||
| synchronized (TelemetryManager.class) { | ||
| if (globallyDisabled) { | ||
| return null; | ||
| } | ||
| localRef = instance; | ||
| if (localRef == null) { | ||
| TelemetryConfiguration config = TelemetryConfiguration.builder().build(); | ||
| TelemetryConfiguration config = | ||
| TelemetryConfiguration.builder().resolveProperties(properties).build(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| ClearcutTransport transport = new ClearcutTransport(config); | ||
| TelemetryBatcher batcher = new TelemetryBatcher(config, transport); | ||
| localRef = new TelemetryManager(batcher); | ||
|
|
@@ -116,4 +144,115 @@ public void close() { | |
| batcher.close(); | ||
| } | ||
| } | ||
|
|
||
| // Package-private test helper to reset the global kill switch between test runs | ||
| static synchronized void resetGlobalDisableForTest() { | ||
| globallyDisabled = false; | ||
| } | ||
|
|
||
| static StatementType toStatementType(QueryStatistics.StatementType bqStatementType) { | ||
| if (bqStatementType == null) { | ||
| return StatementType.STATEMENT_TYPE_UNSPECIFIED; | ||
| } | ||
|
|
||
| EnumValueDescriptor desc = | ||
| StatementType.getDescriptor().findValueByName("STATEMENT_TYPE_" + bqStatementType.name()); | ||
|
|
||
| return desc != null ? StatementType.valueOf(desc) : StatementType.STATEMENT_TYPE_OTHER; | ||
| } | ||
|
Neenu1995 marked this conversation as resolved.
|
||
|
|
||
| static AuthenticationType toAuthenticationType(int oauthType) { | ||
| switch (oauthType) { | ||
| case 0: | ||
| return AuthenticationType.AUTHENTICATION_TYPE_SERVICE_ACCOUNT; | ||
| case 1: | ||
| return AuthenticationType.AUTHENTICATION_TYPE_USER_AUTHENTICATION; | ||
| case 2: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where is int coming from? It doesn't match OAuthType connection properties |
||
| return AuthenticationType.AUTHENTICATION_TYPE_APPLICATION_DEFAULT_CREDENTIALS; | ||
| case 3: | ||
| return AuthenticationType.AUTHENTICATION_TYPE_EXTERNAL; | ||
| case 4: | ||
| return AuthenticationType.AUTHENTICATION_TYPE_TOKEN; | ||
| default: | ||
| return AuthenticationType.AUTHENTICATION_TYPE_CUSTOM; | ||
| } | ||
| } | ||
|
|
||
| static final double[] HISTOGRAM_BOUNDS = { | ||
| 10.0, 50.0, 100.0, 250.0, 500.0, 1000.0, 5000.0, 10000.0 | ||
| }; | ||
|
|
||
| static DurationHistogram toDurationBucketMs(long durationMs) { | ||
| DurationHistogram.Builder builder = | ||
| DurationHistogram.newBuilder().setCount(1).setSum(durationMs); | ||
|
|
||
| int bucketIndex = HISTOGRAM_BOUNDS.length; | ||
| for (int i = 0; i < HISTOGRAM_BOUNDS.length; i++) { | ||
| builder.addExplicitBounds(HISTOGRAM_BOUNDS[i]); | ||
| if (bucketIndex == HISTOGRAM_BOUNDS.length && durationMs < HISTOGRAM_BOUNDS[i]) { | ||
| bucketIndex = i; | ||
| } | ||
| } | ||
| for (int i = 0; i <= HISTOGRAM_BOUNDS.length; i++) { | ||
| builder.addBucketCounts(i == bucketIndex ? 1L : 0L); | ||
| } | ||
| return builder.build(); | ||
| } | ||
|
|
||
| static void recordConnectionAttempt(Status status, int errorCode, AuthenticationType authType) { | ||
| runSafely( | ||
| () -> { | ||
| TelemetryManager mgr = instance; | ||
| if (mgr != null && mgr.getBatcher() != null) { | ||
| mgr.getBatcher() | ||
| .offerConnectionAttempt( | ||
| ConnectionAttempt.newBuilder() | ||
| .setStatus(status) | ||
| .setErrorCode(errorCode) | ||
| .setAuthType(authType) | ||
| .setCount(1) | ||
| .build()); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| static void recordStatementExecution( | ||
| StatementType statementType, | ||
| QueryApiType apiType, | ||
| Status status, | ||
| int errorCode, | ||
| long durationMs) { | ||
| runSafely( | ||
| () -> { | ||
| TelemetryManager mgr = instance; | ||
| if (mgr != null && mgr.getBatcher() != null) { | ||
| mgr.getBatcher() | ||
| .offerStatementExecution( | ||
| StatementExecution.newBuilder() | ||
| .setStatementType(statementType) | ||
| .setQueryApiType(apiType) | ||
| .setStatus(status) | ||
| .setErrorCode(errorCode) | ||
| .setCount(1) | ||
| .setDuration(toDurationBucketMs(durationMs)) | ||
| .build()); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| static void recordFeatureUsage(DriverFeature feature, String customFeatureName) { | ||
| runSafely( | ||
| () -> { | ||
| TelemetryManager mgr = instance; | ||
| if (mgr != null && mgr.getBatcher() != null) { | ||
| mgr.getBatcher() | ||
| .offerFeatureUsage( | ||
| FeatureUsage.newBuilder() | ||
| .setDriverFeature(feature) | ||
| .setCustomFeatureName(customFeatureName == null ? "" : customFeatureName) | ||
| .setCount(1) | ||
| .build()); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This
ifassigns exact same value