| // 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 |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package storage |
| |
| import ( |
| "os" |
| "strconv" |
| "time" |
| |
| "cloud.google.com/go/storage/experimental" |
| storageinternal "cloud.google.com/go/storage/internal" |
| "go.opentelemetry.io/otel/sdk/metric" |
| "google.golang.org/api/option" |
| "google.golang.org/api/option/internaloption" |
| ) |
| |
| const ( |
| dynamicReadReqIncreaseRateEnv = "DYNAMIC_READ_REQ_INCREASE_RATE" |
| dynamicReadReqInitialTimeoutEnv = "DYNAMIC_READ_REQ_INITIAL_TIMEOUT" |
| defaultDynamicReadReqIncreaseRate = 15.0 |
| defaultDynamicReqdReqMaxTimeout = 1 * time.Hour |
| defaultDynamicReadReqMinTimeout = 500 * time.Millisecond |
| defaultTargetPercentile = 0.99 |
| ) |
| |
| func init() { |
| // initialize experimental options |
| storageinternal.WithMetricExporter = withMetricExporter |
| storageinternal.WithMetricInterval = withMetricInterval |
| storageinternal.WithReadStallTimeout = withReadStallTimeout |
| } |
| |
| // getDynamicReadReqIncreaseRateFromEnv returns the value set in the env variable. |
| // It returns defaultDynamicReadReqIncreaseRate if env is not set or the set value is invalid. |
| func getDynamicReadReqIncreaseRateFromEnv() float64 { |
| increaseRate := os.Getenv(dynamicReadReqIncreaseRateEnv) |
| if increaseRate == "" { |
| return defaultDynamicReadReqIncreaseRate |
| } |
| |
| val, err := strconv.ParseFloat(increaseRate, 64) |
| if err != nil { |
| return defaultDynamicReadReqIncreaseRate |
| } |
| return val |
| } |
| |
| // getDynamicReadReqInitialTimeoutSecFromEnv returns the value set in the env variable. |
| // It returns the passed defaultVal if env is not set or the set value is invalid. |
| func getDynamicReadReqInitialTimeoutSecFromEnv(defaultVal time.Duration) time.Duration { |
| initialTimeout := os.Getenv(dynamicReadReqInitialTimeoutEnv) |
| if initialTimeout == "" { |
| return defaultVal |
| } |
| |
| val, err := time.ParseDuration(initialTimeout) |
| if err != nil { |
| return defaultVal |
| } |
| return val |
| } |
| |
| // set through storageClientOptions. |
| type storageConfig struct { |
| useJSONforReads bool |
| readAPIWasSet bool |
| disableClientMetrics bool |
| metricExporter *metric.Exporter |
| metricInterval time.Duration |
| readStallTimeoutConfig *experimental.ReadStallTimeoutConfig |
| } |
| |
| // newStorageConfig generates a new storageConfig with all the given |
| // storageClientOptions applied. |
| func newStorageConfig(opts ...option.ClientOption) storageConfig { |
| var conf storageConfig |
| for _, opt := range opts { |
| if storageOpt, ok := opt.(storageClientOption); ok { |
| storageOpt.ApplyStorageOpt(&conf) |
| } |
| } |
| return conf |
| } |
| |
| // A storageClientOption is an option for a Google Storage client. |
| type storageClientOption interface { |
| option.ClientOption |
| ApplyStorageOpt(*storageConfig) |
| } |
| |
| // WithJSONReads is an option that may be passed to [NewClient]. |
| // It sets the client to use the Cloud Storage JSON API for object |
| // reads. Currently, the default API used for reads is XML, but JSON will |
| // become the default in a future release. |
| // |
| // Setting this option is required to use the GenerationNotMatch condition. We |
| // also recommend using JSON reads to ensure consistency with other client |
| // operations (all of which use JSON by default). |
| // |
| // Note that when this option is set, reads will return a zero date for |
| // [ReaderObjectAttrs].LastModified and may return a different value for |
| // [ReaderObjectAttrs].CacheControl. |
| func WithJSONReads() option.ClientOption { |
| return &withReadAPI{useJSON: true} |
| } |
| |
| // WithXMLReads is an option that may be passed to [NewClient]. |
| // It sets the client to use the Cloud Storage XML API for object reads. |
| // |
| // This is the current default, but the default will switch to JSON in a future |
| // release. |
| func WithXMLReads() option.ClientOption { |
| return &withReadAPI{useJSON: false} |
| } |
| |
| type withReadAPI struct { |
| internaloption.EmbeddableAdapter |
| useJSON bool |
| } |
| |
| func (w *withReadAPI) ApplyStorageOpt(c *storageConfig) { |
| c.useJSONforReads = w.useJSON |
| c.readAPIWasSet = true |
| } |
| |
| type withDisabledClientMetrics struct { |
| internaloption.EmbeddableAdapter |
| disabledClientMetrics bool |
| } |
| |
| // WithDisabledClientMetrics is an option that may be passed to [NewClient]. |
| // gRPC metrics are enabled by default in the GCS client and will export the |
| // gRPC telemetry discussed in [gRFC/66] and [gRFC/78] to |
| // [Google Cloud Monitoring]. The option is used to disable metrics. |
| // Google Cloud Support can use this information to more quickly diagnose |
| // problems related to GCS and gRPC. |
| // Sending this data does not incur any billing charges, and requires minimal |
| // CPU (a single RPC every few minutes) or memory (a few KiB to batch the |
| // telemetry). |
| // |
| // The default is to enable client metrics. To opt-out of metrics collected use |
| // this option. |
| // |
| // [gRFC/66]: https://github.com/grpc/proposal/blob/master/A66-otel-stats.md |
| // [gRFC/78]: https://github.com/grpc/proposal/blob/master/A78-grpc-metrics-wrr-pf-xds.md |
| // [Google Cloud Monitoring]: https://cloud.google.com/monitoring/docs |
| func WithDisabledClientMetrics() option.ClientOption { |
| return &withDisabledClientMetrics{disabledClientMetrics: true} |
| } |
| |
| func (w *withDisabledClientMetrics) ApplyStorageOpt(c *storageConfig) { |
| c.disableClientMetrics = w.disabledClientMetrics |
| } |
| |
| type withMeterOptions struct { |
| internaloption.EmbeddableAdapter |
| // set sampling interval |
| interval time.Duration |
| } |
| |
| func withMetricInterval(interval time.Duration) option.ClientOption { |
| return &withMeterOptions{interval: interval} |
| } |
| |
| func (w *withMeterOptions) ApplyStorageOpt(c *storageConfig) { |
| c.metricInterval = w.interval |
| } |
| |
| type withMetricExporterConfig struct { |
| internaloption.EmbeddableAdapter |
| // exporter override |
| metricExporter *metric.Exporter |
| } |
| |
| func withMetricExporter(ex *metric.Exporter) option.ClientOption { |
| return &withMetricExporterConfig{metricExporter: ex} |
| } |
| |
| func (w *withMetricExporterConfig) ApplyStorageOpt(c *storageConfig) { |
| c.metricExporter = w.metricExporter |
| } |
| |
| // WithReadStallTimeout is an option that may be passed to [NewClient]. |
| // It enables the client to retry the stalled read request, happens as part of |
| // storage.Reader creation. As the name suggest, timeout is adjusted dynamically |
| // based on past observed read-req latencies. |
| // |
| // This is only supported for the read operation and that too for http(XML) client. |
| // Grpc read-operation will be supported soon. |
| func withReadStallTimeout(rstc *experimental.ReadStallTimeoutConfig) option.ClientOption { |
| // TODO (raj-prince): To keep separate dynamicDelay instance for different BucketHandle. |
| // Currently, dynamicTimeout is kept at the client and hence shared across all the |
| // BucketHandle, which is not the ideal state. As latency depends on location of VM |
| // and Bucket, and read latency of different buckets may lie in different range. |
| // Hence having a separate dynamicTimeout instance at BucketHandle level will |
| // be better |
| if rstc.Min == time.Duration(0) { |
| rstc.Min = defaultDynamicReadReqMinTimeout |
| } |
| if rstc.TargetPercentile == 0 { |
| rstc.TargetPercentile = defaultTargetPercentile |
| } |
| return &withReadStallTimeoutConfig{ |
| readStallTimeoutConfig: rstc, |
| } |
| } |
| |
| type withReadStallTimeoutConfig struct { |
| internaloption.EmbeddableAdapter |
| readStallTimeoutConfig *experimental.ReadStallTimeoutConfig |
| } |
| |
| func (wrstc *withReadStallTimeoutConfig) ApplyStorageOpt(config *storageConfig) { |
| config.readStallTimeoutConfig = wrstc.readStallTimeoutConfig |
| } |