From 1c84696ecf00a37277705f2e49d52372254c9189 Mon Sep 17 00:00:00 2001 From: Tom Parker Date: Thu, 15 Aug 2024 17:51:01 -0400 Subject: [PATCH 01/10] Add telemetry middleware --- go.mod | 34 +- go.sum | 195 ++++- internal/pkg/api/api.go | 16 +- vendor/github.com/cespare/xxhash/v2/README.md | 2 + vendor/github.com/cespare/xxhash/v2/xxhash.go | 29 +- .../cespare/xxhash/v2/xxhash_asm.go | 2 +- .../cespare/xxhash/v2/xxhash_other.go | 2 +- .../cespare/xxhash/v2/xxhash_safe.go | 2 +- .../cespare/xxhash/v2/xxhash_unsafe.go | 2 +- vendor/github.com/go-chi/telemetry/LICENSE | 201 +++++ vendor/github.com/go-chi/telemetry/README.md | 7 + .../github.com/go-chi/telemetry/collector.go | 99 +++ vendor/github.com/go-chi/telemetry/config.go | 15 + vendor/github.com/go-chi/telemetry/http.go | 30 + .../github.com/go-chi/telemetry/ip_utils.go | 97 +++ .../github.com/go-chi/telemetry/telemetry.go | 185 ++++ vendor/github.com/golang/mock/AUTHORS | 12 + vendor/github.com/golang/mock/CONTRIBUTORS | 37 + vendor/github.com/golang/mock/LICENSE | 202 +++++ .../golang/mock/mockgen/model/model.go | 495 +++++++++++ .../prometheus/client_model/go/metrics.pb.go | 195 +++-- .../prometheus/common/expfmt/decode.go | 6 +- .../prometheus/common/expfmt/encode.go | 10 +- .../prometheus/common/expfmt/expfmt.go | 22 +- .../common/expfmt/openmetrics_create.go | 202 ++++- .../prometheus/common/model/labelset.go | 11 - .../common/model/labelset_string.go | 45 + .../common/model/labelset_string_go120.go | 39 + .../prometheus/common/model/metric.go | 1 + .../prometheus/procfs/Makefile.common | 16 +- vendor/github.com/prometheus/procfs/crypto.go | 2 +- .../github.com/prometheus/procfs/meminfo.go | 218 +++-- .../prometheus/procfs/net_ip_socket.go | 26 +- .../prometheus/procfs/net_tls_stat.go | 119 +++ .../github.com/prometheus/procfs/proc_stat.go | 7 + vendor/github.com/twmb/murmur3/.gitignore | 1 + vendor/github.com/twmb/murmur3/LICENSE | 49 ++ vendor/github.com/twmb/murmur3/README.md | 129 +++ vendor/github.com/twmb/murmur3/murmur.go | 58 ++ vendor/github.com/twmb/murmur3/murmur128.go | 182 ++++ .../github.com/twmb/murmur3/murmur128_amd64.s | 247 ++++++ .../github.com/twmb/murmur3/murmur128_decl.go | 36 + .../github.com/twmb/murmur3/murmur128_gen.go | 236 ++++++ vendor/github.com/twmb/murmur3/murmur32.go | 100 +++ .../github.com/twmb/murmur3/murmur32_gen.go | 108 +++ vendor/github.com/twmb/murmur3/murmur64.go | 70 ++ vendor/github.com/uber-go/tally/v4/.gitignore | 34 + .../github.com/uber-go/tally/v4/.travis.yml | 19 + vendor/github.com/uber-go/tally/v4/LICENSE | 21 + vendor/github.com/uber-go/tally/v4/Makefile | 98 +++ vendor/github.com/uber-go/tally/v4/README.md | 212 +++++ .../uber-go/tally/v4/check_license.sh | 16 + .../github.com/uber-go/tally/v4/generate.go | 26 + .../github.com/uber-go/tally/v4/histogram.go | 384 +++++++++ .../tally/v4/internal/identity/accumulator.go | 131 +++ vendor/github.com/uber-go/tally/v4/key_gen.go | 107 +++ vendor/github.com/uber-go/tally/v4/pool.go | 63 ++ .../uber-go/tally/v4/prometheus/README.md | 118 +++ .../uber-go/tally/v4/prometheus/config.go | 176 ++++ .../uber-go/tally/v4/prometheus/reporter.go | 610 +++++++++++++ .../uber-go/tally/v4/prometheus/sanitize.go | 42 + .../github.com/uber-go/tally/v4/reporter.go | 140 +++ .../github.com/uber-go/tally/v4/sanitize.go | 194 +++++ vendor/github.com/uber-go/tally/v4/scope.go | 800 ++++++++++++++++++ .../uber-go/tally/v4/scope_registry.go | 344 ++++++++ vendor/github.com/uber-go/tally/v4/stats.go | 563 ++++++++++++ vendor/github.com/uber-go/tally/v4/types.go | 157 ++++ vendor/github.com/uber-go/tally/v4/version.go | 24 + vendor/go.uber.org/atomic/CHANGELOG.md | 27 + vendor/go.uber.org/atomic/bool.go | 11 +- vendor/go.uber.org/atomic/duration.go | 11 +- vendor/go.uber.org/atomic/error.go | 23 +- vendor/go.uber.org/atomic/error_ext.go | 4 +- vendor/go.uber.org/atomic/float32.go | 77 ++ vendor/go.uber.org/atomic/float32_ext.go | 76 ++ vendor/go.uber.org/atomic/float64.go | 2 +- vendor/go.uber.org/atomic/float64_ext.go | 35 +- vendor/go.uber.org/atomic/int32.go | 9 +- vendor/go.uber.org/atomic/int64.go | 9 +- vendor/go.uber.org/atomic/nocmp.go | 12 +- vendor/go.uber.org/atomic/pointer_go118.go | 31 + .../atomic/pointer_go118_pre119.go | 60 ++ vendor/go.uber.org/atomic/pointer_go119.go | 61 ++ vendor/go.uber.org/atomic/string.go | 30 +- vendor/go.uber.org/atomic/string_ext.go | 17 +- vendor/go.uber.org/atomic/time.go | 2 +- vendor/go.uber.org/atomic/uint32.go | 9 +- vendor/go.uber.org/atomic/uint64.go | 9 +- vendor/go.uber.org/atomic/uintptr.go | 9 +- vendor/go.uber.org/atomic/unsafe_pointer.go | 9 +- vendor/go.uber.org/atomic/value.go | 4 +- vendor/golang.org/x/sys/unix/mmap_nomremap.go | 2 +- .../x/sys/unix/syscall_zos_s390x.go | 8 + .../x/sys/windows/syscall_windows.go | 82 ++ .../golang.org/x/sys/windows/types_windows.go | 24 + .../x/sys/windows/zsyscall_windows.go | 126 ++- vendor/modules.txt | 30 +- 97 files changed, 8577 insertions(+), 310 deletions(-) create mode 100644 vendor/github.com/go-chi/telemetry/LICENSE create mode 100644 vendor/github.com/go-chi/telemetry/README.md create mode 100644 vendor/github.com/go-chi/telemetry/collector.go create mode 100644 vendor/github.com/go-chi/telemetry/config.go create mode 100644 vendor/github.com/go-chi/telemetry/http.go create mode 100644 vendor/github.com/go-chi/telemetry/ip_utils.go create mode 100644 vendor/github.com/go-chi/telemetry/telemetry.go create mode 100644 vendor/github.com/golang/mock/AUTHORS create mode 100644 vendor/github.com/golang/mock/CONTRIBUTORS create mode 100644 vendor/github.com/golang/mock/LICENSE create mode 100644 vendor/github.com/golang/mock/mockgen/model/model.go create mode 100644 vendor/github.com/prometheus/common/model/labelset_string.go create mode 100644 vendor/github.com/prometheus/common/model/labelset_string_go120.go create mode 100644 vendor/github.com/prometheus/procfs/net_tls_stat.go create mode 100644 vendor/github.com/twmb/murmur3/.gitignore create mode 100644 vendor/github.com/twmb/murmur3/LICENSE create mode 100644 vendor/github.com/twmb/murmur3/README.md create mode 100644 vendor/github.com/twmb/murmur3/murmur.go create mode 100644 vendor/github.com/twmb/murmur3/murmur128.go create mode 100644 vendor/github.com/twmb/murmur3/murmur128_amd64.s create mode 100644 vendor/github.com/twmb/murmur3/murmur128_decl.go create mode 100644 vendor/github.com/twmb/murmur3/murmur128_gen.go create mode 100644 vendor/github.com/twmb/murmur3/murmur32.go create mode 100644 vendor/github.com/twmb/murmur3/murmur32_gen.go create mode 100644 vendor/github.com/twmb/murmur3/murmur64.go create mode 100644 vendor/github.com/uber-go/tally/v4/.gitignore create mode 100644 vendor/github.com/uber-go/tally/v4/.travis.yml create mode 100644 vendor/github.com/uber-go/tally/v4/LICENSE create mode 100644 vendor/github.com/uber-go/tally/v4/Makefile create mode 100644 vendor/github.com/uber-go/tally/v4/README.md create mode 100644 vendor/github.com/uber-go/tally/v4/check_license.sh create mode 100644 vendor/github.com/uber-go/tally/v4/generate.go create mode 100644 vendor/github.com/uber-go/tally/v4/histogram.go create mode 100644 vendor/github.com/uber-go/tally/v4/internal/identity/accumulator.go create mode 100644 vendor/github.com/uber-go/tally/v4/key_gen.go create mode 100644 vendor/github.com/uber-go/tally/v4/pool.go create mode 100644 vendor/github.com/uber-go/tally/v4/prometheus/README.md create mode 100644 vendor/github.com/uber-go/tally/v4/prometheus/config.go create mode 100644 vendor/github.com/uber-go/tally/v4/prometheus/reporter.go create mode 100644 vendor/github.com/uber-go/tally/v4/prometheus/sanitize.go create mode 100644 vendor/github.com/uber-go/tally/v4/reporter.go create mode 100644 vendor/github.com/uber-go/tally/v4/sanitize.go create mode 100644 vendor/github.com/uber-go/tally/v4/scope.go create mode 100644 vendor/github.com/uber-go/tally/v4/scope_registry.go create mode 100644 vendor/github.com/uber-go/tally/v4/stats.go create mode 100644 vendor/github.com/uber-go/tally/v4/types.go create mode 100644 vendor/github.com/uber-go/tally/v4/version.go create mode 100644 vendor/go.uber.org/atomic/float32.go create mode 100644 vendor/go.uber.org/atomic/float32_ext.go create mode 100644 vendor/go.uber.org/atomic/pointer_go118.go create mode 100644 vendor/go.uber.org/atomic/pointer_go118_pre119.go create mode 100644 vendor/go.uber.org/atomic/pointer_go119.go diff --git a/go.mod b/go.mod index a075da0..b4c3b83 100644 --- a/go.mod +++ b/go.mod @@ -2,39 +2,45 @@ module bws-cache go 1.22.3 +require ( + github.com/bitwarden/sdk-go v0.1.1 + github.com/go-chi/chi/v5 v5.1.0 + github.com/go-chi/telemetry v0.3.4 + github.com/google/uuid v1.6.0 + github.com/jellydator/ttlcache/v3 v3.2.0 + github.com/pkg/errors v0.9.1 + github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.19.0 +) + require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/bitwarden/sdk-go v0.1.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-chi/chi/v5 v5.1.0 // indirect - github.com/gofrs/uuid v4.4.0+incompatible // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/golang/mock v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jellydator/ttlcache/v3 v3.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.19.1 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.52.3 // indirect + github.com/prometheus/procfs v0.13.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.19.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.uber.org/atomic v1.9.0 // indirect + github.com/twmb/murmur3 v1.1.8 // indirect + github.com/uber-go/tally/v4 v4.1.16 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 0fd1c1d..f062f90 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,60 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bitwarden/sdk-go v0.1.1 h1:Fn7d0SuThIEwaIecg3SRBM6RUbUyQQ7x7Ex+qrcLbMA= github.com/bitwarden/sdk-go v0.1.1/go.mod h1:Gp2ADXAL0XQ3GO3zxAv503xSlL6ORPf0VZg2J+yQ6jU= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cactus/go-statsd-client/v5 v5.0.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= -github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/go-chi/telemetry v0.3.4 h1:iCe1lbqP4pOOYkxyy3Y2LjkNA1iMgp1owp0JYgdTRhM= +github.com/go-chi/telemetry v0.3.4/go.mod h1:N+qwgqriyLwEPFyXAjj22GMdDlNuCaEourUPJfZBoPw= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -21,28 +63,75 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE= github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZAQSA= +github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= +github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -56,33 +145,111 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= +github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= +github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= +github.com/uber-go/tally/v4 v4.1.16 h1:by2hveWRh/cUReButk6ns1sHK/hiKry7BuOV6iY16XI= +github.com/uber-go/tally/v4 v4.1.16/go.mod h1:RW5DgqsyEPs0lA4b0YNf4zKj7DveKHd73hnO6zVlyW0= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/validator.v2 v2.0.0-20200605151824-2b28d334fa05/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index 8f63a24..e6a46ac 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -12,10 +12,18 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/telemetry" "github.com/pkg/errors" - "github.com/prometheus/client_golang/prometheus/promhttp" ) +var ( + AppMetrics = &BwsMetrics{telemetry.NewScope("app")} +) + +type BwsMetrics struct { + *telemetry.Scope +} + type API struct { SecretTTL time.Duration WebTTL time.Duration @@ -35,6 +43,11 @@ func New(config *config.Config) http.Handler { router.Use(middleware.Logger) router.Use(middleware.Recoverer) router.Use(middleware.Timeout(config.WebTTL)) + // telemetry.Collector middleware mounts /metrics endpoint + // with prometheus metrics collector. + router.Use(telemetry.Collector(telemetry.Config{ + AllowAny: true, + }, []string{"/"})) // path prefix filters records generic http request metrics // Enable profiler router.Mount("/debug", middleware.Profiler()) @@ -52,7 +65,6 @@ func New(config *config.Config) http.Handler { r.Get("/{secret_key}", api.getSecretByKey) }) router.Get("/reset", api.resetConnection) - router.Handle("/metrics", promhttp.Handler()) return router } diff --git a/vendor/github.com/cespare/xxhash/v2/README.md b/vendor/github.com/cespare/xxhash/v2/README.md index 8bf0e5b..33c8830 100644 --- a/vendor/github.com/cespare/xxhash/v2/README.md +++ b/vendor/github.com/cespare/xxhash/v2/README.md @@ -70,3 +70,5 @@ benchstat <(go test -benchtime 500ms -count 15 -bench 'Sum64$') - [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics) - [FreeCache](https://github.com/coocood/freecache) - [FastCache](https://github.com/VictoriaMetrics/fastcache) +- [Ristretto](https://github.com/dgraph-io/ristretto) +- [Badger](https://github.com/dgraph-io/badger) diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash.go b/vendor/github.com/cespare/xxhash/v2/xxhash.go index a9e0d45..78bddf1 100644 --- a/vendor/github.com/cespare/xxhash/v2/xxhash.go +++ b/vendor/github.com/cespare/xxhash/v2/xxhash.go @@ -19,10 +19,13 @@ const ( // Store the primes in an array as well. // // The consts are used when possible in Go code to avoid MOVs but we need a -// contiguous array of the assembly code. +// contiguous array for the assembly code. var primes = [...]uint64{prime1, prime2, prime3, prime4, prime5} // Digest implements hash.Hash64. +// +// Note that a zero-valued Digest is not ready to receive writes. +// Call Reset or create a Digest using New before calling other methods. type Digest struct { v1 uint64 v2 uint64 @@ -33,19 +36,31 @@ type Digest struct { n int // how much of mem is used } -// New creates a new Digest that computes the 64-bit xxHash algorithm. +// New creates a new Digest with a zero seed. func New() *Digest { + return NewWithSeed(0) +} + +// NewWithSeed creates a new Digest with the given seed. +func NewWithSeed(seed uint64) *Digest { var d Digest - d.Reset() + d.ResetWithSeed(seed) return &d } // Reset clears the Digest's state so that it can be reused. +// It uses a seed value of zero. func (d *Digest) Reset() { - d.v1 = primes[0] + prime2 - d.v2 = prime2 - d.v3 = 0 - d.v4 = -primes[0] + d.ResetWithSeed(0) +} + +// ResetWithSeed clears the Digest's state so that it can be reused. +// It uses the given seed to initialize the state. +func (d *Digest) ResetWithSeed(seed uint64) { + d.v1 = seed + prime1 + prime2 + d.v2 = seed + prime2 + d.v3 = seed + d.v4 = seed - prime1 d.total = 0 d.n = 0 } diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash_asm.go b/vendor/github.com/cespare/xxhash/v2/xxhash_asm.go index 9216e0a..78f95f2 100644 --- a/vendor/github.com/cespare/xxhash/v2/xxhash_asm.go +++ b/vendor/github.com/cespare/xxhash/v2/xxhash_asm.go @@ -6,7 +6,7 @@ package xxhash -// Sum64 computes the 64-bit xxHash digest of b. +// Sum64 computes the 64-bit xxHash digest of b with a zero seed. // //go:noescape func Sum64(b []byte) uint64 diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash_other.go b/vendor/github.com/cespare/xxhash/v2/xxhash_other.go index 26df13b..118e49e 100644 --- a/vendor/github.com/cespare/xxhash/v2/xxhash_other.go +++ b/vendor/github.com/cespare/xxhash/v2/xxhash_other.go @@ -3,7 +3,7 @@ package xxhash -// Sum64 computes the 64-bit xxHash digest of b. +// Sum64 computes the 64-bit xxHash digest of b with a zero seed. func Sum64(b []byte) uint64 { // A simpler version would be // d := New() diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash_safe.go b/vendor/github.com/cespare/xxhash/v2/xxhash_safe.go index e86f1b5..05f5e7d 100644 --- a/vendor/github.com/cespare/xxhash/v2/xxhash_safe.go +++ b/vendor/github.com/cespare/xxhash/v2/xxhash_safe.go @@ -5,7 +5,7 @@ package xxhash -// Sum64String computes the 64-bit xxHash digest of s. +// Sum64String computes the 64-bit xxHash digest of s with a zero seed. func Sum64String(s string) uint64 { return Sum64([]byte(s)) } diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go b/vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go index 1c1638f..cf9d42a 100644 --- a/vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go +++ b/vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go @@ -33,7 +33,7 @@ import ( // // See https://github.com/golang/go/issues/42739 for discussion. -// Sum64String computes the 64-bit xxHash digest of s. +// Sum64String computes the 64-bit xxHash digest of s with a zero seed. // It may be faster than Sum64([]byte(s)) by avoiding a copy. func Sum64String(s string) uint64 { b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)})) diff --git a/vendor/github.com/go-chi/telemetry/LICENSE b/vendor/github.com/go-chi/telemetry/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/vendor/github.com/go-chi/telemetry/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/go-chi/telemetry/README.md b/vendor/github.com/go-chi/telemetry/README.md new file mode 100644 index 0000000..6a91f93 --- /dev/null +++ b/vendor/github.com/go-chi/telemetry/README.md @@ -0,0 +1,7 @@ +telemetry +========= + +Go package to simplify recording metrics in a Prometheus compatible format. +Its based on github.com/uber-go/tally/v4/prometheus. + +Checkout the [example](_examples/basic/main.go) for more details. diff --git a/vendor/github.com/go-chi/telemetry/collector.go b/vendor/github.com/go-chi/telemetry/collector.go new file mode 100644 index 0000000..9f007f9 --- /dev/null +++ b/vendor/github.com/go-chi/telemetry/collector.go @@ -0,0 +1,99 @@ +package telemetry + +import ( + "net" + "net/http" + "strings" + "time" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" +) + +// Collector creates a handler that exposes a /metrics endpoint. Passing +// an array of strings to pathPrefixFilters will help reduce the noise on the service +// from random Internet traffic; that is, only the path prefixes will be measured. +func Collector(cfg Config, optPathPrefixFilters ...[]string) func(next http.Handler) http.Handler { + if (!cfg.AllowAny && !cfg.AllowInternal) && (cfg.Username == "" || cfg.Password == "") { + return func(next http.Handler) http.Handler { + return next + } + } + + pathPrefixFilters := []string{} + if len(optPathPrefixFilters) > 0 { + for _, v := range optPathPrefixFilters[0] { + pathPrefixFilters = append(pathPrefixFilters, v) + } + } + + authHandler := middleware.BasicAuth( + "metrics", + map[string]string{cfg.Username: cfg.Password}, + ) + + metricsHandler := chi.Chain( + func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + // Maybe allow internal traffic + if cfg.AllowInternal { + ipAddress := net.ParseIP(getIPAddress(r)) + if isPrivateSubnet(ipAddress) { + next.ServeHTTP(w, r) + return + } + } + + // Maybw allow basic auth traffic + if cfg.Username != "" && cfg.Password != "" { + authHandler(next).ServeHTTP(w, r) + return + } + + // Maybe allow any + if cfg.AllowAny { + next.ServeHTTP(w, r) + return + } + + w.WriteHeader(http.StatusNotFound) + }) + }, + func(http.Handler) http.Handler { + return reporter.HTTPHandler() + }, + ) + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" && strings.EqualFold(r.URL.Path, "/metrics") { + // serve metrics page + metricsHandler.Handler(next).ServeHTTP(w, r) + return + } + + // check path filters + if len(pathPrefixFilters) > 0 { + found := false + for _, p := range pathPrefixFilters { + if strings.HasPrefix(r.URL.Path, p) { + found = true + break + } + } + if !found && r.URL.Path != "/" { + // skip measurement of the http request + next.ServeHTTP(w, r) + return + } + } + + // measure request + ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) + defer sample(time.Now().UTC(), r, ww) + + next.ServeHTTP(ww, r) + }) + } +} diff --git a/vendor/github.com/go-chi/telemetry/config.go b/vendor/github.com/go-chi/telemetry/config.go new file mode 100644 index 0000000..d5f6706 --- /dev/null +++ b/vendor/github.com/go-chi/telemetry/config.go @@ -0,0 +1,15 @@ +package telemetry + +// Config represents settings for the telemetry tool. +type Config struct { + // If any of these values are not provided, measurements won't be exposed. + Username string `toml:"username"` + Password string `toml:"password"` + + // Allow any traffic. Ie. if username/password are not specified, but AllowAny + // is true, then the metrics endpoint will be available. + AllowAny bool `toml:"allow_any"` + + // Allow internal private subnet traffic + AllowInternal bool `toml:"allow_internal"` +} diff --git a/vendor/github.com/go-chi/telemetry/http.go b/vendor/github.com/go-chi/telemetry/http.go new file mode 100644 index 0000000..ca653a6 --- /dev/null +++ b/vendor/github.com/go-chi/telemetry/http.go @@ -0,0 +1,30 @@ +package telemetry + +import ( + "fmt" + "net/http" + "time" + "unicode/utf8" + + "github.com/go-chi/chi/v5/middleware" +) + +var httpMetrics = NewScope("http") + +func sample(start time.Time, r *http.Request, ww middleware.WrapResponseWriter) { + status := ww.Status() + if status == 0 { // TODO: see why we have status = 0 under test conditions (this came up during benchmarks for some of the requests) + status = http.StatusOK + } + // prometheus errors if string is not uft8 encoded + if !utf8.ValidString(r.URL.Path) { + return + } + labels := map[string]string{ + "endpoint": fmt.Sprintf("%s %s", r.Method, r.URL.Path), + "status": fmt.Sprintf("%d", status), + } + + httpMetrics.RecordDuration("request", labels, start, time.Now().UTC()) + httpMetrics.RecordHit("requests", labels) +} diff --git a/vendor/github.com/go-chi/telemetry/ip_utils.go b/vendor/github.com/go-chi/telemetry/ip_utils.go new file mode 100644 index 0000000..d4e573d --- /dev/null +++ b/vendor/github.com/go-chi/telemetry/ip_utils.go @@ -0,0 +1,97 @@ +package telemetry + +import ( + "bytes" + "net" + "net/http" + "strings" +) + +// getIPAddress gets the real ip address from headers +// if not found it returns r.RemoteAddr +func getIPAddress(r *http.Request) string { + ipHeaders := []string{ + "True-Client-IP", + "X-Forwarded-For", + "X-Real-Ip", + } + for _, h := range ipHeaders { + addresses := strings.Split(r.Header.Get(h), ",") + // march from right to left + for i := len(addresses) - 1; i >= 0; i-- { + // header can contain spaces too, strip those out. + ip := strings.TrimSpace(addresses[i]) + if ip == "" { + continue + } + return ip + } + } + return ipRemoteAddr(r.RemoteAddr) +} + +// isPrivateSubnet - check to see if this ip is in a private subnet +func isPrivateSubnet(ipAddress net.IP) bool { + if ipCheck := ipAddress.To4(); ipCheck != nil { + // iterate over all our ranges + for _, r := range privateRanges { + // check if this ip is in a private range + if inRange(r, ipAddress) { + return true + } + } + } + // TODO: implement ipv6 ranges + return false +} + +func ipRemoteAddr(remoteAddr string) string { + ip, _, err := net.SplitHostPort(remoteAddr) + if err != nil { + return "" + } + return ip +} + +// ipRange - a structure that holds the start and end of a range of ip addresses +type ipRange struct { + start net.IP + end net.IP +} + +// inRange - check to see if a given ip address is within a range given +func inRange(r ipRange, ipAddress net.IP) bool { + // strcmp type byte comparison + if bytes.Compare(ipAddress, r.start) >= 0 && bytes.Compare(ipAddress, r.end) < 0 { + return true + } + return false +} + +// refer https://datatracker.ietf.org/doc/html/rfc1918#section-3 +var privateRanges = []ipRange{ + { + start: net.ParseIP("10.0.0.0"), + end: net.ParseIP("10.255.255.255"), + }, + { + start: net.ParseIP("100.64.0.0"), + end: net.ParseIP("100.127.255.255"), + }, + { + start: net.ParseIP("172.16.0.0"), + end: net.ParseIP("172.31.255.255"), + }, + { + start: net.ParseIP("192.0.0.0"), + end: net.ParseIP("192.0.0.255"), + }, + { + start: net.ParseIP("192.168.0.0"), + end: net.ParseIP("192.168.255.255"), + }, + { + start: net.ParseIP("198.18.0.0"), + end: net.ParseIP("198.19.255.255"), + }, +} diff --git a/vendor/github.com/go-chi/telemetry/telemetry.go b/vendor/github.com/go-chi/telemetry/telemetry.go new file mode 100644 index 0000000..bfbf856 --- /dev/null +++ b/vendor/github.com/go-chi/telemetry/telemetry.go @@ -0,0 +1,185 @@ +package telemetry + +import ( + "fmt" + "io" + "time" + + "github.com/uber-go/tally/v4" + "github.com/uber-go/tally/v4/prometheus" +) + +var reporter = prometheus.NewReporter(prometheus.Options{}) + +var defaultBucketsForIntegerValues = tally.ValueBuckets{ + 1, + 2, + 5, + 7, + 9, + 10, + 50, + 100, +} + +var defaultBucketFactorsForDurations = []float64{ + 0.001, + 0.005, + 0.01, + 0.05, + 0.1, + 0.25, + 0.5, + 0.75, + 0.9, + 0.95, + 0.99, + 1, + 2.5, + 5, + 10, + 25, + 50, + 100, +} + +// Scope represents the measurements scope for an application. +type Scope struct { + scope tally.Scope + closer io.Closer +} + +// NewScope creates a scope for an application. Receives a scope +// argument (a single-word) that is used as a prefix for all measurements. +func NewScope(scope string) *Scope { + s, closer := newRootScope(tally.ScopeOptions{ + Prefix: scope, + }, 1*time.Second) + return &Scope{ + scope: s, + closer: closer, + } +} + +// Close closes the scope and stops reporting. +func (n *Scope) Close() error { + return n.closer.Close() +} + +// RecordHit increases a hit counter. This is ideal for counting HTTP requests +// or other events that are incremented by one each time. +// +// RecordHit adds the "_total" suffix to the name of the measurement. +func (n *Scope) RecordHit(measurement string, tags map[string]string) { + record := n.scope.Tagged(tags).Counter(fmt.Sprintf(measurement + "_total")) + record.Inc(1.0) +} + +func (n *Scope) RecordIncrementValue(measurement string, tags map[string]string, value int64) { + record := n.scope.Tagged(tags).Counter(fmt.Sprintf(measurement + "_total")) + record.Inc(value) +} + +// RecordGauge sets the value of a measurement that can go up or down over +// time. +// +// RecordGauge measures a prometheus raw type and no suffix is added to the +// measurement. +func (n *Scope) RecordGauge(measurement string, tags map[string]string, value float64) { + record := n.scope.Tagged(tags).Gauge(measurement) + record.Update(value) +} + +// RecordSize records a numeric unit-less value that can go up or down. Use it +// when it's more important to know the last value of said size. This is useful +// to measure things like the size of a queue. +// +// RecordSize adds the "_size" prefix to the name of the measurement. +func (n *Scope) RecordSize(measurement string, tags map[string]string, value float64) { + n.RecordGauge(fmt.Sprintf(measurement+"_size"), tags, value) +} + +// RecordIntegerValue records a numeric unit-less value that can go up or down. +// Use it when is important to see how the value evolved over time. +// +// RecordIntegerValue uses an histogram configured with buckets that priorize +// values closer to zero. +func (n *Scope) RecordIntegerValue(measurement string, tags map[string]string, value int) { + record := n.scope.Tagged(tags). + Histogram(fmt.Sprintf(measurement+"_value"), defaultBucketsForIntegerValues) + record.RecordValue(float64(value)) +} + +// RecordValue records a numeric unit-less value that can go up or down. Use it +// when is important to see how the value evolved over time. +// +// RecordValue measures a prometheus raw type and no suffix is added to the measurement. +func (n *Scope) RecordValue(measurement string, tags map[string]string, value float64) { + n.RecordValueWithBuckets(measurement, tags, value, nil) +} + +// RecordValueWithBuckets records a numeric unit-less value that can go up or +// down. Use it when is important to see how the value evolved over time. +// +// RecordValueWithBuckets adds the "_value" suffix to the name of the measurement. +func (n *Scope) RecordValueWithBuckets(measurement string, tags map[string]string, value float64, buckets []float64) { + record := n.scope.Tagged(tags). + Histogram(fmt.Sprintf(measurement+"_value"), tally.ValueBuckets(buckets)) + record.RecordValue(value) +} + +// RecordDuration records an elapsed time. Use it when is important to see how +// values. Use it when is important to see how a value evolved over time, for +// instance request durations, the time it takes for a task to finish, etc. +// +// RecordDuration adds the "_duration_seconds" prefix to the name of the +// measurement. +func (n *Scope) RecordDuration(measurement string, tags map[string]string, start time.Time, stop time.Time) { + n.RecordDurationWithResolution(measurement, tags, start, stop, 0) +} + +// RecordDurationWithResolution records the elapsed duration between two time +// values. Use it when is important to see how a value evolved over time, for +// instance request durations, the time it takes for a task to finish, etc. +// +// The resolution parameter can be any value, this value will be taken as base +// to build buckets. +// +// RecordDurationWithResolution adds the "_duration_seconds" prefix to the name +// of the measurement. +func (n *Scope) RecordDurationWithResolution(measurement string, tags map[string]string, timeA time.Time, timeB time.Time, resolution time.Duration) { + var buckets tally.Buckets + + if resolution <= 0 { + resolution = time.Second + } + + unit := float64(resolution) + durations := make([]time.Duration, len(defaultBucketFactorsForDurations)) + for i := range durations { + durations[i] = time.Duration(int64(unit * defaultBucketFactorsForDurations[i])) + } + buckets = tally.DurationBuckets(durations) + + record := n.scope.Tagged(tags).Histogram( + fmt.Sprintf(measurement+"_duration_seconds"), + buckets, + ) + elapsed := timeB.Sub(timeA) + if elapsed < 0 { + elapsed = elapsed * -1 + } + record.RecordDuration(elapsed) +} + +func (n *Scope) RecordSpan(measurement string, tags map[string]string) tally.Stopwatch { + return n.scope.Timer(measurement + "_span").Start() +} + +func newRootScope(opts tally.ScopeOptions, interval time.Duration) (tally.Scope, io.Closer) { + opts.CachedReporter = reporter + opts.Separator = prometheus.DefaultSeparator + opts.SanitizeOptions = &prometheus.DefaultSanitizerOpts + opts.OmitCardinalityMetrics = true + return tally.NewRootScope(opts, interval) +} diff --git a/vendor/github.com/golang/mock/AUTHORS b/vendor/github.com/golang/mock/AUTHORS new file mode 100644 index 0000000..660b8cc --- /dev/null +++ b/vendor/github.com/golang/mock/AUTHORS @@ -0,0 +1,12 @@ +# This is the official list of GoMock authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as +# Name or Organization +# The email address is not required for organizations. + +# Please keep the list sorted. + +Alex Reece +Google Inc. diff --git a/vendor/github.com/golang/mock/CONTRIBUTORS b/vendor/github.com/golang/mock/CONTRIBUTORS new file mode 100644 index 0000000..def849c --- /dev/null +++ b/vendor/github.com/golang/mock/CONTRIBUTORS @@ -0,0 +1,37 @@ +# This is the official list of people who can contribute (and typically +# have contributed) code to the gomock repository. +# The AUTHORS file lists the copyright holders; this file +# lists people. For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. +# +# The submission process automatically checks to make sure +# that people submitting code are listed in this file (by email address). +# +# Names should be added to this file only after verifying that +# the individual or the individual's organization has agreed to +# the appropriate Contributor License Agreement, found here: +# +# http://code.google.com/legal/individual-cla-v1.0.html +# http://code.google.com/legal/corporate-cla-v1.0.html +# +# The agreement for individuals can be filled out on the web. +# +# When adding J Random Contributor's name to this file, +# either J's name or J's organization's name should be +# added to the AUTHORS file, depending on whether the +# individual or corporate CLA was used. + +# Names should be added to this file like so: +# Name +# +# An entry with two email addresses specifies that the +# first address should be used in the submit logs and +# that the second address should be recognized as the +# same person when interacting with Rietveld. + +# Please keep the list sorted. + +Aaron Jacobs +Alex Reece +David Symonds +Ryan Barrett diff --git a/vendor/github.com/golang/mock/LICENSE b/vendor/github.com/golang/mock/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/vendor/github.com/golang/mock/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/golang/mock/mockgen/model/model.go b/vendor/github.com/golang/mock/mockgen/model/model.go new file mode 100644 index 0000000..2c6a62c --- /dev/null +++ b/vendor/github.com/golang/mock/mockgen/model/model.go @@ -0,0 +1,495 @@ +// Copyright 2012 Google Inc. +// +// 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 model contains the data model necessary for generating mock implementations. +package model + +import ( + "encoding/gob" + "fmt" + "io" + "reflect" + "strings" +) + +// pkgPath is the importable path for package model +const pkgPath = "github.com/golang/mock/mockgen/model" + +// Package is a Go package. It may be a subset. +type Package struct { + Name string + PkgPath string + Interfaces []*Interface + DotImports []string +} + +// Print writes the package name and its exported interfaces. +func (pkg *Package) Print(w io.Writer) { + _, _ = fmt.Fprintf(w, "package %s\n", pkg.Name) + for _, intf := range pkg.Interfaces { + intf.Print(w) + } +} + +// Imports returns the imports needed by the Package as a set of import paths. +func (pkg *Package) Imports() map[string]bool { + im := make(map[string]bool) + for _, intf := range pkg.Interfaces { + intf.addImports(im) + } + return im +} + +// Interface is a Go interface. +type Interface struct { + Name string + Methods []*Method +} + +// Print writes the interface name and its methods. +func (intf *Interface) Print(w io.Writer) { + _, _ = fmt.Fprintf(w, "interface %s\n", intf.Name) + for _, m := range intf.Methods { + m.Print(w) + } +} + +func (intf *Interface) addImports(im map[string]bool) { + for _, m := range intf.Methods { + m.addImports(im) + } +} + +// AddMethod adds a new method, de-duplicating by method name. +func (intf *Interface) AddMethod(m *Method) { + for _, me := range intf.Methods { + if me.Name == m.Name { + return + } + } + intf.Methods = append(intf.Methods, m) +} + +// Method is a single method of an interface. +type Method struct { + Name string + In, Out []*Parameter + Variadic *Parameter // may be nil +} + +// Print writes the method name and its signature. +func (m *Method) Print(w io.Writer) { + _, _ = fmt.Fprintf(w, " - method %s\n", m.Name) + if len(m.In) > 0 { + _, _ = fmt.Fprintf(w, " in:\n") + for _, p := range m.In { + p.Print(w) + } + } + if m.Variadic != nil { + _, _ = fmt.Fprintf(w, " ...:\n") + m.Variadic.Print(w) + } + if len(m.Out) > 0 { + _, _ = fmt.Fprintf(w, " out:\n") + for _, p := range m.Out { + p.Print(w) + } + } +} + +func (m *Method) addImports(im map[string]bool) { + for _, p := range m.In { + p.Type.addImports(im) + } + if m.Variadic != nil { + m.Variadic.Type.addImports(im) + } + for _, p := range m.Out { + p.Type.addImports(im) + } +} + +// Parameter is an argument or return parameter of a method. +type Parameter struct { + Name string // may be empty + Type Type +} + +// Print writes a method parameter. +func (p *Parameter) Print(w io.Writer) { + n := p.Name + if n == "" { + n = `""` + } + _, _ = fmt.Fprintf(w, " - %v: %v\n", n, p.Type.String(nil, "")) +} + +// Type is a Go type. +type Type interface { + String(pm map[string]string, pkgOverride string) string + addImports(im map[string]bool) +} + +func init() { + gob.Register(&ArrayType{}) + gob.Register(&ChanType{}) + gob.Register(&FuncType{}) + gob.Register(&MapType{}) + gob.Register(&NamedType{}) + gob.Register(&PointerType{}) + + // Call gob.RegisterName to make sure it has the consistent name registered + // for both gob decoder and encoder. + // + // For a non-pointer type, gob.Register will try to get package full path by + // calling rt.PkgPath() for a name to register. If your project has vendor + // directory, it is possible that PkgPath will get a path like this: + // ../../../vendor/github.com/golang/mock/mockgen/model + gob.RegisterName(pkgPath+".PredeclaredType", PredeclaredType("")) +} + +// ArrayType is an array or slice type. +type ArrayType struct { + Len int // -1 for slices, >= 0 for arrays + Type Type +} + +func (at *ArrayType) String(pm map[string]string, pkgOverride string) string { + s := "[]" + if at.Len > -1 { + s = fmt.Sprintf("[%d]", at.Len) + } + return s + at.Type.String(pm, pkgOverride) +} + +func (at *ArrayType) addImports(im map[string]bool) { at.Type.addImports(im) } + +// ChanType is a channel type. +type ChanType struct { + Dir ChanDir // 0, 1 or 2 + Type Type +} + +func (ct *ChanType) String(pm map[string]string, pkgOverride string) string { + s := ct.Type.String(pm, pkgOverride) + if ct.Dir == RecvDir { + return "<-chan " + s + } + if ct.Dir == SendDir { + return "chan<- " + s + } + return "chan " + s +} + +func (ct *ChanType) addImports(im map[string]bool) { ct.Type.addImports(im) } + +// ChanDir is a channel direction. +type ChanDir int + +// Constants for channel directions. +const ( + RecvDir ChanDir = 1 + SendDir ChanDir = 2 +) + +// FuncType is a function type. +type FuncType struct { + In, Out []*Parameter + Variadic *Parameter // may be nil +} + +func (ft *FuncType) String(pm map[string]string, pkgOverride string) string { + args := make([]string, len(ft.In)) + for i, p := range ft.In { + args[i] = p.Type.String(pm, pkgOverride) + } + if ft.Variadic != nil { + args = append(args, "..."+ft.Variadic.Type.String(pm, pkgOverride)) + } + rets := make([]string, len(ft.Out)) + for i, p := range ft.Out { + rets[i] = p.Type.String(pm, pkgOverride) + } + retString := strings.Join(rets, ", ") + if nOut := len(ft.Out); nOut == 1 { + retString = " " + retString + } else if nOut > 1 { + retString = " (" + retString + ")" + } + return "func(" + strings.Join(args, ", ") + ")" + retString +} + +func (ft *FuncType) addImports(im map[string]bool) { + for _, p := range ft.In { + p.Type.addImports(im) + } + if ft.Variadic != nil { + ft.Variadic.Type.addImports(im) + } + for _, p := range ft.Out { + p.Type.addImports(im) + } +} + +// MapType is a map type. +type MapType struct { + Key, Value Type +} + +func (mt *MapType) String(pm map[string]string, pkgOverride string) string { + return "map[" + mt.Key.String(pm, pkgOverride) + "]" + mt.Value.String(pm, pkgOverride) +} + +func (mt *MapType) addImports(im map[string]bool) { + mt.Key.addImports(im) + mt.Value.addImports(im) +} + +// NamedType is an exported type in a package. +type NamedType struct { + Package string // may be empty + Type string +} + +func (nt *NamedType) String(pm map[string]string, pkgOverride string) string { + if pkgOverride == nt.Package { + return nt.Type + } + prefix := pm[nt.Package] + if prefix != "" { + return prefix + "." + nt.Type + } + + return nt.Type +} + +func (nt *NamedType) addImports(im map[string]bool) { + if nt.Package != "" { + im[nt.Package] = true + } +} + +// PointerType is a pointer to another type. +type PointerType struct { + Type Type +} + +func (pt *PointerType) String(pm map[string]string, pkgOverride string) string { + return "*" + pt.Type.String(pm, pkgOverride) +} +func (pt *PointerType) addImports(im map[string]bool) { pt.Type.addImports(im) } + +// PredeclaredType is a predeclared type such as "int". +type PredeclaredType string + +func (pt PredeclaredType) String(map[string]string, string) string { return string(pt) } +func (pt PredeclaredType) addImports(map[string]bool) {} + +// The following code is intended to be called by the program generated by ../reflect.go. + +// InterfaceFromInterfaceType returns a pointer to an interface for the +// given reflection interface type. +func InterfaceFromInterfaceType(it reflect.Type) (*Interface, error) { + if it.Kind() != reflect.Interface { + return nil, fmt.Errorf("%v is not an interface", it) + } + intf := &Interface{} + + for i := 0; i < it.NumMethod(); i++ { + mt := it.Method(i) + // TODO: need to skip unexported methods? or just raise an error? + m := &Method{ + Name: mt.Name, + } + + var err error + m.In, m.Variadic, m.Out, err = funcArgsFromType(mt.Type) + if err != nil { + return nil, err + } + + intf.AddMethod(m) + } + + return intf, nil +} + +// t's Kind must be a reflect.Func. +func funcArgsFromType(t reflect.Type) (in []*Parameter, variadic *Parameter, out []*Parameter, err error) { + nin := t.NumIn() + if t.IsVariadic() { + nin-- + } + var p *Parameter + for i := 0; i < nin; i++ { + p, err = parameterFromType(t.In(i)) + if err != nil { + return + } + in = append(in, p) + } + if t.IsVariadic() { + p, err = parameterFromType(t.In(nin).Elem()) + if err != nil { + return + } + variadic = p + } + for i := 0; i < t.NumOut(); i++ { + p, err = parameterFromType(t.Out(i)) + if err != nil { + return + } + out = append(out, p) + } + return +} + +func parameterFromType(t reflect.Type) (*Parameter, error) { + tt, err := typeFromType(t) + if err != nil { + return nil, err + } + return &Parameter{Type: tt}, nil +} + +var errorType = reflect.TypeOf((*error)(nil)).Elem() + +var byteType = reflect.TypeOf(byte(0)) + +func typeFromType(t reflect.Type) (Type, error) { + // Hack workaround for https://golang.org/issue/3853. + // This explicit check should not be necessary. + if t == byteType { + return PredeclaredType("byte"), nil + } + + if imp := t.PkgPath(); imp != "" { + return &NamedType{ + Package: impPath(imp), + Type: t.Name(), + }, nil + } + + // only unnamed or predeclared types after here + + // Lots of types have element types. Let's do the parsing and error checking for all of them. + var elemType Type + switch t.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice: + var err error + elemType, err = typeFromType(t.Elem()) + if err != nil { + return nil, err + } + } + + switch t.Kind() { + case reflect.Array: + return &ArrayType{ + Len: t.Len(), + Type: elemType, + }, nil + case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, + reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String: + return PredeclaredType(t.Kind().String()), nil + case reflect.Chan: + var dir ChanDir + switch t.ChanDir() { + case reflect.RecvDir: + dir = RecvDir + case reflect.SendDir: + dir = SendDir + } + return &ChanType{ + Dir: dir, + Type: elemType, + }, nil + case reflect.Func: + in, variadic, out, err := funcArgsFromType(t) + if err != nil { + return nil, err + } + return &FuncType{ + In: in, + Out: out, + Variadic: variadic, + }, nil + case reflect.Interface: + // Two special interfaces. + if t.NumMethod() == 0 { + return PredeclaredType("interface{}"), nil + } + if t == errorType { + return PredeclaredType("error"), nil + } + case reflect.Map: + kt, err := typeFromType(t.Key()) + if err != nil { + return nil, err + } + return &MapType{ + Key: kt, + Value: elemType, + }, nil + case reflect.Ptr: + return &PointerType{ + Type: elemType, + }, nil + case reflect.Slice: + return &ArrayType{ + Len: -1, + Type: elemType, + }, nil + case reflect.Struct: + if t.NumField() == 0 { + return PredeclaredType("struct{}"), nil + } + } + + // TODO: Struct, UnsafePointer + return nil, fmt.Errorf("can't yet turn %v (%v) into a model.Type", t, t.Kind()) +} + +// impPath sanitizes the package path returned by `PkgPath` method of a reflect Type so that +// it is importable. PkgPath might return a path that includes "vendor". These paths do not +// compile, so we need to remove everything up to and including "/vendor/". +// See https://github.com/golang/go/issues/12019. +func impPath(imp string) string { + if strings.HasPrefix(imp, "vendor/") { + imp = "/" + imp + } + if i := strings.LastIndex(imp, "/vendor/"); i != -1 { + imp = imp[i+len("/vendor/"):] + } + return imp +} + +// ErrorInterface represent built-in error interface. +var ErrorInterface = Interface{ + Name: "error", + Methods: []*Method{ + { + Name: "Error", + Out: []*Parameter{ + { + Name: "", + Type: PredeclaredType("string"), + }, + }, + }, + }, +} diff --git a/vendor/github.com/prometheus/client_model/go/metrics.pb.go b/vendor/github.com/prometheus/client_model/go/metrics.pb.go index cee360d..2f15490 100644 --- a/vendor/github.com/prometheus/client_model/go/metrics.pb.go +++ b/vendor/github.com/prometheus/client_model/go/metrics.pb.go @@ -483,6 +483,8 @@ type Histogram struct { // histograms. PositiveDelta []int64 `protobuf:"zigzag64,13,rep,name=positive_delta,json=positiveDelta" json:"positive_delta,omitempty"` // Count delta of each bucket compared to previous one (or to zero for 1st bucket). PositiveCount []float64 `protobuf:"fixed64,14,rep,name=positive_count,json=positiveCount" json:"positive_count,omitempty"` // Absolute count of each bucket. + // Only used for native histograms. These exemplars MUST have a timestamp. + Exemplars []*Exemplar `protobuf:"bytes,16,rep,name=exemplars" json:"exemplars,omitempty"` } func (x *Histogram) Reset() { @@ -622,6 +624,13 @@ func (x *Histogram) GetPositiveCount() []float64 { return nil } +func (x *Histogram) GetExemplars() []*Exemplar { + if x != nil { + return x.Exemplars + } + return nil +} + // A Bucket of a conventional histogram, each of which is treated as // an individual counter-like time series by Prometheus. type Bucket struct { @@ -923,6 +932,7 @@ type MetricFamily struct { Help *string `protobuf:"bytes,2,opt,name=help" json:"help,omitempty"` Type *MetricType `protobuf:"varint,3,opt,name=type,enum=io.prometheus.client.MetricType" json:"type,omitempty"` Metric []*Metric `protobuf:"bytes,4,rep,name=metric" json:"metric,omitempty"` + Unit *string `protobuf:"bytes,5,opt,name=unit" json:"unit,omitempty"` } func (x *MetricFamily) Reset() { @@ -985,6 +995,13 @@ func (x *MetricFamily) GetMetric() []*Metric { return nil } +func (x *MetricFamily) GetUnit() string { + if x != nil && x.Unit != nil { + return *x.Unit + } + return "" +} + var File_io_prometheus_client_metrics_proto protoreflect.FileDescriptor var file_io_prometheus_client_metrics_proto_rawDesc = []byte{ @@ -1028,7 +1045,7 @@ var file_io_prometheus_client_metrics_proto_rawDesc = []byte{ 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x1f, 0x0a, 0x07, 0x55, 0x6e, 0x74, 0x79, 0x70, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xac, 0x05, 0x0a, 0x09, 0x48, + 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xea, 0x05, 0x0a, 0x09, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x73, @@ -1071,79 +1088,84 @@ var file_io_prometheus_client_metrics_proto_rawDesc = []byte{ 0x03, 0x28, 0x12, 0x52, 0x0d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x01, 0x52, 0x0d, 0x70, 0x6f, 0x73, 0x69, - 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc6, 0x01, 0x0a, 0x06, 0x42, 0x75, - 0x63, 0x6b, 0x65, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, - 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, - 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x34, 0x0a, 0x16, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, - 0x14, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x70, 0x70, 0x65, 0x72, 0x5f, 0x62, - 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x75, 0x70, 0x70, 0x65, - 0x72, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x3a, 0x0a, 0x08, 0x65, 0x78, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, - 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, - 0x45, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x52, 0x08, 0x65, 0x78, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x72, 0x22, 0x3c, 0x0a, 0x0a, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x70, 0x61, 0x6e, - 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x11, - 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, - 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, - 0x22, 0x91, 0x01, 0x0a, 0x08, 0x45, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x12, 0x35, 0x0a, + 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x09, 0x65, 0x78, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, + 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x52, 0x09, 0x65, 0x78, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x73, 0x22, 0xc6, 0x01, 0x0a, 0x06, 0x42, 0x75, 0x63, 0x6b, + 0x65, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x63, 0x75, + 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x34, 0x0a, + 0x16, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x14, 0x63, + 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x46, 0x6c, + 0x6f, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x70, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x6f, 0x75, + 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x75, 0x70, 0x70, 0x65, 0x72, 0x42, + 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x3a, 0x0a, 0x08, 0x65, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, + 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x78, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x52, 0x08, 0x65, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, + 0x22, 0x3c, 0x0a, 0x0a, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x16, + 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x11, 0x52, 0x06, + 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0x91, + 0x01, 0x0a, 0x08, 0x45, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x12, 0x35, 0x0a, 0x05, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69, 0x6f, 0x2e, + 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x22, 0xff, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x35, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x6c, - 0x61, 0x62, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x22, 0xff, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, - 0x35, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, - 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, 0x69, 0x72, 0x52, - 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x31, 0x0a, 0x05, 0x67, 0x61, 0x75, 0x67, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, - 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x47, 0x61, 0x75, - 0x67, 0x65, 0x52, 0x05, 0x67, 0x61, 0x75, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e, + 0x61, 0x62, 0x65, 0x6c, 0x12, 0x31, 0x0a, 0x05, 0x67, 0x61, 0x75, 0x67, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, + 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x47, 0x61, 0x75, 0x67, 0x65, + 0x52, 0x05, 0x67, 0x61, 0x75, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, + 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, + 0x12, 0x37, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, + 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x37, 0x0a, 0x07, 0x75, 0x6e, 0x74, + 0x79, 0x70, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x65, 0x72, 0x12, 0x37, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, - 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x37, 0x0a, 0x07, 0x75, - 0x6e, 0x74, 0x79, 0x70, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, - 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x2e, 0x55, 0x6e, 0x74, 0x79, 0x70, 0x65, 0x64, 0x52, 0x07, 0x75, 0x6e, 0x74, - 0x79, 0x70, 0x65, 0x64, 0x12, 0x3d, 0x0a, 0x09, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, - 0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, - 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x48, - 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x09, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x4d, 0x73, 0x22, 0xa2, 0x01, 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, - 0x65, 0x6c, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x65, 0x6c, 0x70, 0x12, - 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, - 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, - 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2a, 0x62, 0x0a, 0x0a, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x55, - 0x4e, 0x54, 0x45, 0x52, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x41, 0x55, 0x47, 0x45, 0x10, - 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x4d, 0x4d, 0x41, 0x52, 0x59, 0x10, 0x02, 0x12, 0x0b, - 0x0a, 0x07, 0x55, 0x4e, 0x54, 0x59, 0x50, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x48, - 0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x47, 0x41, - 0x55, 0x47, 0x45, 0x5f, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x10, 0x05, 0x42, - 0x52, 0x0a, 0x14, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, - 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2f, 0x63, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x67, 0x6f, 0x3b, 0x69, - 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x5f, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, + 0x74, 0x2e, 0x55, 0x6e, 0x74, 0x79, 0x70, 0x65, 0x64, 0x52, 0x07, 0x75, 0x6e, 0x74, 0x79, 0x70, + 0x65, 0x64, 0x12, 0x3d, 0x0a, 0x09, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, + 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x48, 0x69, 0x73, + 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x09, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, + 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6d, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x4d, 0x73, 0x22, 0xb6, 0x01, 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x46, + 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x6c, + 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x65, 0x6c, 0x70, 0x12, 0x34, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x69, 0x6f, + 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, + 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x6e, 0x69, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x6e, 0x69, 0x74, 0x2a, 0x62, 0x0a, + 0x0a, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x43, + 0x4f, 0x55, 0x4e, 0x54, 0x45, 0x52, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x41, 0x55, 0x47, + 0x45, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x4d, 0x4d, 0x41, 0x52, 0x59, 0x10, 0x02, + 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x54, 0x59, 0x50, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0d, 0x0a, + 0x09, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, + 0x47, 0x41, 0x55, 0x47, 0x45, 0x5f, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x10, + 0x05, 0x42, 0x52, 0x0a, 0x14, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, + 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, + 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x67, 0x6f, + 0x3b, 0x69, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x5f, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, } var ( @@ -1185,22 +1207,23 @@ var file_io_prometheus_client_metrics_proto_depIdxs = []int32{ 13, // 5: io.prometheus.client.Histogram.created_timestamp:type_name -> google.protobuf.Timestamp 9, // 6: io.prometheus.client.Histogram.negative_span:type_name -> io.prometheus.client.BucketSpan 9, // 7: io.prometheus.client.Histogram.positive_span:type_name -> io.prometheus.client.BucketSpan - 10, // 8: io.prometheus.client.Bucket.exemplar:type_name -> io.prometheus.client.Exemplar - 1, // 9: io.prometheus.client.Exemplar.label:type_name -> io.prometheus.client.LabelPair - 13, // 10: io.prometheus.client.Exemplar.timestamp:type_name -> google.protobuf.Timestamp - 1, // 11: io.prometheus.client.Metric.label:type_name -> io.prometheus.client.LabelPair - 2, // 12: io.prometheus.client.Metric.gauge:type_name -> io.prometheus.client.Gauge - 3, // 13: io.prometheus.client.Metric.counter:type_name -> io.prometheus.client.Counter - 5, // 14: io.prometheus.client.Metric.summary:type_name -> io.prometheus.client.Summary - 6, // 15: io.prometheus.client.Metric.untyped:type_name -> io.prometheus.client.Untyped - 7, // 16: io.prometheus.client.Metric.histogram:type_name -> io.prometheus.client.Histogram - 0, // 17: io.prometheus.client.MetricFamily.type:type_name -> io.prometheus.client.MetricType - 11, // 18: io.prometheus.client.MetricFamily.metric:type_name -> io.prometheus.client.Metric - 19, // [19:19] is the sub-list for method output_type - 19, // [19:19] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 10, // 8: io.prometheus.client.Histogram.exemplars:type_name -> io.prometheus.client.Exemplar + 10, // 9: io.prometheus.client.Bucket.exemplar:type_name -> io.prometheus.client.Exemplar + 1, // 10: io.prometheus.client.Exemplar.label:type_name -> io.prometheus.client.LabelPair + 13, // 11: io.prometheus.client.Exemplar.timestamp:type_name -> google.protobuf.Timestamp + 1, // 12: io.prometheus.client.Metric.label:type_name -> io.prometheus.client.LabelPair + 2, // 13: io.prometheus.client.Metric.gauge:type_name -> io.prometheus.client.Gauge + 3, // 14: io.prometheus.client.Metric.counter:type_name -> io.prometheus.client.Counter + 5, // 15: io.prometheus.client.Metric.summary:type_name -> io.prometheus.client.Summary + 6, // 16: io.prometheus.client.Metric.untyped:type_name -> io.prometheus.client.Untyped + 7, // 17: io.prometheus.client.Metric.histogram:type_name -> io.prometheus.client.Histogram + 0, // 18: io.prometheus.client.MetricFamily.type:type_name -> io.prometheus.client.MetricType + 11, // 19: io.prometheus.client.MetricFamily.metric:type_name -> io.prometheus.client.Metric + 20, // [20:20] is the sub-list for method output_type + 20, // [20:20] is the sub-list for method input_type + 20, // [20:20] is the sub-list for extension type_name + 20, // [20:20] is the sub-list for extension extendee + 0, // [0:20] is the sub-list for field type_name } func init() { file_io_prometheus_client_metrics_proto_init() } diff --git a/vendor/github.com/prometheus/common/expfmt/decode.go b/vendor/github.com/prometheus/common/expfmt/decode.go index b2b89b0..25cfaa2 100644 --- a/vendor/github.com/prometheus/common/expfmt/decode.go +++ b/vendor/github.com/prometheus/common/expfmt/decode.go @@ -75,14 +75,14 @@ func ResponseFormat(h http.Header) Format { func NewDecoder(r io.Reader, format Format) Decoder { switch format.FormatType() { case TypeProtoDelim: - return &protoDecoder{r: r} + return &protoDecoder{r: bufio.NewReader(r)} } return &textDecoder{r: r} } // protoDecoder implements the Decoder interface for protocol buffers. type protoDecoder struct { - r io.Reader + r protodelim.Reader } // Decode implements the Decoder interface. @@ -90,7 +90,7 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error { opts := protodelim.UnmarshalOptions{ MaxSize: -1, } - if err := opts.UnmarshalFrom(bufio.NewReader(d.r), v); err != nil { + if err := opts.UnmarshalFrom(d.r, v); err != nil { return err } if !model.IsValidMetricName(model.LabelValue(v.GetName())) { diff --git a/vendor/github.com/prometheus/common/expfmt/encode.go b/vendor/github.com/prometheus/common/expfmt/encode.go index 8fd8061..7f6cbe7 100644 --- a/vendor/github.com/prometheus/common/expfmt/encode.go +++ b/vendor/github.com/prometheus/common/expfmt/encode.go @@ -139,7 +139,13 @@ func NegotiateIncludingOpenMetrics(h http.Header) Format { // interface is kept for backwards compatibility. // In cases where the Format does not allow for UTF-8 names, the global // NameEscapingScheme will be applied. -func NewEncoder(w io.Writer, format Format) Encoder { +// +// NewEncoder can be called with additional options to customize the OpenMetrics text output. +// For example: +// NewEncoder(w, FmtOpenMetrics_1_0_0, WithCreatedLines()) +// +// Extra options are ignored for all other formats. +func NewEncoder(w io.Writer, format Format, options ...EncoderOption) Encoder { escapingScheme := format.ToEscapingScheme() switch format.FormatType() { @@ -178,7 +184,7 @@ func NewEncoder(w io.Writer, format Format) Encoder { case TypeOpenMetrics: return encoderCloser{ encode: func(v *dto.MetricFamily) error { - _, err := MetricFamilyToOpenMetrics(w, model.EscapeMetricFamily(v, escapingScheme)) + _, err := MetricFamilyToOpenMetrics(w, model.EscapeMetricFamily(v, escapingScheme), options...) return err }, close: func() error { diff --git a/vendor/github.com/prometheus/common/expfmt/expfmt.go b/vendor/github.com/prometheus/common/expfmt/expfmt.go index 6fc9555..051b38c 100644 --- a/vendor/github.com/prometheus/common/expfmt/expfmt.go +++ b/vendor/github.com/prometheus/common/expfmt/expfmt.go @@ -15,6 +15,7 @@ package expfmt import ( + "fmt" "strings" "github.com/prometheus/common/model" @@ -63,7 +64,7 @@ const ( type FormatType int const ( - TypeUnknown = iota + TypeUnknown FormatType = iota TypeProtoCompact TypeProtoDelim TypeProtoText @@ -73,7 +74,8 @@ const ( // NewFormat generates a new Format from the type provided. Mostly used for // tests, most Formats should be generated as part of content negotiation in -// encode.go. +// encode.go. If a type has more than one version, the latest version will be +// returned. func NewFormat(t FormatType) Format { switch t { case TypeProtoCompact: @@ -91,13 +93,21 @@ func NewFormat(t FormatType) Format { } } +// NewOpenMetricsFormat generates a new OpenMetrics format matching the +// specified version number. +func NewOpenMetricsFormat(version string) (Format, error) { + if version == OpenMetricsVersion_0_0_1 { + return fmtOpenMetrics_0_0_1, nil + } + if version == OpenMetricsVersion_1_0_0 { + return fmtOpenMetrics_1_0_0, nil + } + return fmtUnknown, fmt.Errorf("unknown open metrics version string") +} + // FormatType deduces an overall FormatType for the given format. func (f Format) FormatType() FormatType { toks := strings.Split(string(f), ";") - if len(toks) < 2 { - return TypeUnknown - } - params := make(map[string]string) for i, t := range toks { if i == 0 { diff --git a/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go b/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go index 5622578..353c5e9 100644 --- a/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go +++ b/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go @@ -22,11 +22,47 @@ import ( "strconv" "strings" + "google.golang.org/protobuf/types/known/timestamppb" + "github.com/prometheus/common/model" dto "github.com/prometheus/client_model/go" ) +type encoderOption struct { + withCreatedLines bool + withUnit bool +} + +type EncoderOption func(*encoderOption) + +// WithCreatedLines is an EncoderOption that configures the OpenMetrics encoder +// to include _created lines (See +// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#counter-1). +// Created timestamps can improve the accuracy of series reset detection, but +// come with a bandwidth cost. +// +// At the time of writing, created timestamp ingestion is still experimental in +// Prometheus and need to be enabled with the feature-flag +// `--feature-flag=created-timestamp-zero-ingestion`, and breaking changes are +// still possible. Therefore, it is recommended to use this feature with caution. +func WithCreatedLines() EncoderOption { + return func(t *encoderOption) { + t.withCreatedLines = true + } +} + +// WithUnit is an EncoderOption enabling a set unit to be written to the output +// and to be added to the metric name, if it's not there already, as a suffix. +// Without opting in this way, the unit will not be added to the metric name and, +// on top of that, the unit will not be passed onto the output, even if it +// were declared in the *dto.MetricFamily struct, i.e. even if in.Unit !=nil. +func WithUnit() EncoderOption { + return func(t *encoderOption) { + t.withUnit = true + } +} + // MetricFamilyToOpenMetrics converts a MetricFamily proto message into the // OpenMetrics text format and writes the resulting lines to 'out'. It returns // the number of bytes written and any error encountered. The output will have @@ -59,20 +95,34 @@ import ( // Prometheus to OpenMetrics or vice versa: // // - Counters are expected to have the `_total` suffix in their metric name. In -// the output, the suffix will be truncated from the `# TYPE` and `# HELP` -// line. A counter with a missing `_total` suffix is not an error. However, +// the output, the suffix will be truncated from the `# TYPE`, `# HELP` and `# UNIT` +// lines. A counter with a missing `_total` suffix is not an error. However, // its type will be set to `unknown` in that case to avoid invalid OpenMetrics // output. // -// - No support for the following (optional) features: `# UNIT` line, `_created` -// line, info type, stateset type, gaugehistogram type. +// - According to the OM specs, the `# UNIT` line is optional, but if populated, +// the unit has to be present in the metric name as its suffix: +// (see https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#unit). +// However, in order to accommodate any potential scenario where such a change in the +// metric name is not desirable, the users are here given the choice of either explicitly +// opt in, in case they wish for the unit to be included in the output AND in the metric name +// as a suffix (see the description of the WithUnit function above), +// or not to opt in, in case they don't want for any of that to happen. +// +// - No support for the following (optional) features: info type, +// stateset type, gaugehistogram type. // // - The size of exemplar labels is not checked (i.e. it's possible to create // exemplars that are larger than allowed by the OpenMetrics specification). // // - The value of Counters is not checked. (OpenMetrics doesn't allow counters // with a `NaN` value.) -func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int, err error) { +func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...EncoderOption) (written int, err error) { + toOM := encoderOption{} + for _, option := range options { + option(&toOM) + } + name := in.GetName() if name == "" { return 0, fmt.Errorf("MetricFamily has no name: %s", in) @@ -95,12 +145,15 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int } var ( - n int - metricType = in.GetType() - shortName = name + n int + metricType = in.GetType() + compliantName = name ) - if metricType == dto.MetricType_COUNTER && strings.HasSuffix(shortName, "_total") { - shortName = name[:len(name)-6] + if metricType == dto.MetricType_COUNTER && strings.HasSuffix(compliantName, "_total") { + compliantName = name[:len(name)-6] + } + if toOM.withUnit && in.Unit != nil && !strings.HasSuffix(compliantName, fmt.Sprintf("_%s", *in.Unit)) { + compliantName = compliantName + fmt.Sprintf("_%s", *in.Unit) } // Comments, first HELP, then TYPE. @@ -110,7 +163,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int if err != nil { return } - n, err = writeName(w, shortName) + n, err = writeName(w, compliantName) written += n if err != nil { return @@ -136,7 +189,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int if err != nil { return } - n, err = writeName(w, shortName) + n, err = writeName(w, compliantName) written += n if err != nil { return @@ -163,55 +216,89 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int if err != nil { return } + if toOM.withUnit && in.Unit != nil { + n, err = w.WriteString("# UNIT ") + written += n + if err != nil { + return + } + n, err = writeName(w, compliantName) + written += n + if err != nil { + return + } + + err = w.WriteByte(' ') + written++ + if err != nil { + return + } + n, err = writeEscapedString(w, *in.Unit, true) + written += n + if err != nil { + return + } + err = w.WriteByte('\n') + written++ + if err != nil { + return + } + } + + var createdTsBytesWritten int // Finally the samples, one line for each. + if metricType == dto.MetricType_COUNTER && strings.HasSuffix(name, "_total") { + compliantName = compliantName + "_total" + } for _, metric := range in.Metric { switch metricType { case dto.MetricType_COUNTER: if metric.Counter == nil { return written, fmt.Errorf( - "expected counter in metric %s %s", name, metric, + "expected counter in metric %s %s", compliantName, metric, ) } - // Note that we have ensured above that either the name - // ends on `_total` or that the rendered type is - // `unknown`. Therefore, no `_total` must be added here. n, err = writeOpenMetricsSample( - w, name, "", metric, "", 0, + w, compliantName, "", metric, "", 0, metric.Counter.GetValue(), 0, false, metric.Counter.Exemplar, ) + if toOM.withCreatedLines && metric.Counter.CreatedTimestamp != nil { + createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "_total", metric, "", 0, metric.Counter.GetCreatedTimestamp()) + n += createdTsBytesWritten + } case dto.MetricType_GAUGE: if metric.Gauge == nil { return written, fmt.Errorf( - "expected gauge in metric %s %s", name, metric, + "expected gauge in metric %s %s", compliantName, metric, ) } n, err = writeOpenMetricsSample( - w, name, "", metric, "", 0, + w, compliantName, "", metric, "", 0, metric.Gauge.GetValue(), 0, false, nil, ) case dto.MetricType_UNTYPED: if metric.Untyped == nil { return written, fmt.Errorf( - "expected untyped in metric %s %s", name, metric, + "expected untyped in metric %s %s", compliantName, metric, ) } n, err = writeOpenMetricsSample( - w, name, "", metric, "", 0, + w, compliantName, "", metric, "", 0, metric.Untyped.GetValue(), 0, false, nil, ) case dto.MetricType_SUMMARY: if metric.Summary == nil { return written, fmt.Errorf( - "expected summary in metric %s %s", name, metric, + "expected summary in metric %s %s", compliantName, metric, ) } for _, q := range metric.Summary.Quantile { n, err = writeOpenMetricsSample( - w, name, "", metric, + w, compliantName, "", metric, model.QuantileLabel, q.GetQuantile(), q.GetValue(), 0, false, nil, @@ -222,7 +309,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int } } n, err = writeOpenMetricsSample( - w, name, "_sum", metric, "", 0, + w, compliantName, "_sum", metric, "", 0, metric.Summary.GetSampleSum(), 0, false, nil, ) @@ -231,20 +318,24 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int return } n, err = writeOpenMetricsSample( - w, name, "_count", metric, "", 0, + w, compliantName, "_count", metric, "", 0, 0, metric.Summary.GetSampleCount(), true, nil, ) + if toOM.withCreatedLines && metric.Summary.CreatedTimestamp != nil { + createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "", metric, "", 0, metric.Summary.GetCreatedTimestamp()) + n += createdTsBytesWritten + } case dto.MetricType_HISTOGRAM: if metric.Histogram == nil { return written, fmt.Errorf( - "expected histogram in metric %s %s", name, metric, + "expected histogram in metric %s %s", compliantName, metric, ) } infSeen := false for _, b := range metric.Histogram.Bucket { n, err = writeOpenMetricsSample( - w, name, "_bucket", metric, + w, compliantName, "_bucket", metric, model.BucketLabel, b.GetUpperBound(), 0, b.GetCumulativeCount(), true, b.Exemplar, @@ -259,7 +350,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int } if !infSeen { n, err = writeOpenMetricsSample( - w, name, "_bucket", metric, + w, compliantName, "_bucket", metric, model.BucketLabel, math.Inf(+1), 0, metric.Histogram.GetSampleCount(), true, nil, @@ -270,7 +361,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int } } n, err = writeOpenMetricsSample( - w, name, "_sum", metric, "", 0, + w, compliantName, "_sum", metric, "", 0, metric.Histogram.GetSampleSum(), 0, false, nil, ) @@ -279,13 +370,17 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int return } n, err = writeOpenMetricsSample( - w, name, "_count", metric, "", 0, + w, compliantName, "_count", metric, "", 0, 0, metric.Histogram.GetSampleCount(), true, nil, ) + if toOM.withCreatedLines && metric.Histogram.CreatedTimestamp != nil { + createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "", metric, "", 0, metric.Histogram.GetCreatedTimestamp()) + n += createdTsBytesWritten + } default: return written, fmt.Errorf( - "unexpected type in metric %s %s", name, metric, + "unexpected type in metric %s %s", compliantName, metric, ) } written += n @@ -350,7 +445,7 @@ func writeOpenMetricsSample( return written, err } } - if exemplar != nil { + if exemplar != nil && len(exemplar.Label) > 0 { n, err = writeExemplar(w, exemplar) written += n if err != nil { @@ -473,6 +568,49 @@ func writeOpenMetricsNameAndLabelPairs( return written, nil } +// writeOpenMetricsCreated writes the created timestamp for a single time series +// following OpenMetrics text format to w, given the metric name, the metric proto +// message itself, optionally a suffix to be removed, e.g. '_total' for counters, +// an additional label name with a float64 value (use empty string as label name if +// not required) and the timestamp that represents the created timestamp. +// The function returns the number of bytes written and any error encountered. +func writeOpenMetricsCreated(w enhancedWriter, + name, suffixToTrim string, metric *dto.Metric, + additionalLabelName string, additionalLabelValue float64, + createdTimestamp *timestamppb.Timestamp, +) (int, error) { + written := 0 + n, err := writeOpenMetricsNameAndLabelPairs( + w, strings.TrimSuffix(name, suffixToTrim)+"_created", metric.Label, additionalLabelName, additionalLabelValue, + ) + written += n + if err != nil { + return written, err + } + + err = w.WriteByte(' ') + written++ + if err != nil { + return written, err + } + + // TODO(beorn7): Format this directly from components of ts to + // avoid overflow/underflow and precision issues of the float + // conversion. + n, err = writeOpenMetricsFloat(w, float64(createdTimestamp.AsTime().UnixNano())/1e9) + written += n + if err != nil { + return written, err + } + + err = w.WriteByte('\n') + written++ + if err != nil { + return written, err + } + return written, nil +} + // writeExemplar writes the provided exemplar in OpenMetrics format to w. The // function returns the number of bytes written and any error encountered. func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) { diff --git a/vendor/github.com/prometheus/common/model/labelset.go b/vendor/github.com/prometheus/common/model/labelset.go index 6eda08a..d0ad88d 100644 --- a/vendor/github.com/prometheus/common/model/labelset.go +++ b/vendor/github.com/prometheus/common/model/labelset.go @@ -17,7 +17,6 @@ import ( "encoding/json" "fmt" "sort" - "strings" ) // A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet @@ -129,16 +128,6 @@ func (l LabelSet) Merge(other LabelSet) LabelSet { return result } -func (l LabelSet) String() string { - lstrs := make([]string, 0, len(l)) - for l, v := range l { - lstrs = append(lstrs, fmt.Sprintf("%s=%q", l, v)) - } - - sort.Strings(lstrs) - return fmt.Sprintf("{%s}", strings.Join(lstrs, ", ")) -} - // Fingerprint returns the LabelSet's fingerprint. func (ls LabelSet) Fingerprint() Fingerprint { return labelSetToFingerprint(ls) diff --git a/vendor/github.com/prometheus/common/model/labelset_string.go b/vendor/github.com/prometheus/common/model/labelset_string.go new file mode 100644 index 0000000..174753e --- /dev/null +++ b/vendor/github.com/prometheus/common/model/labelset_string.go @@ -0,0 +1,45 @@ +// Copyright 2024 The Prometheus Authors +// 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. + +//go:build go1.21 + +package model + +import ( + "bytes" + "sort" + "strconv" +) + +// String will look like `{foo="bar", more="less"}`. Names are sorted alphabetically. +func (l LabelSet) String() string { + var lna [32]string // On stack to avoid memory allocation for sorting names. + labelNames := lna[:0] + for name := range l { + labelNames = append(labelNames, string(name)) + } + sort.Strings(labelNames) + var bytea [1024]byte // On stack to avoid memory allocation while building the output. + b := bytes.NewBuffer(bytea[:0]) + b.WriteByte('{') + for i, name := range labelNames { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(name) + b.WriteByte('=') + b.Write(strconv.AppendQuote(b.AvailableBuffer(), string(l[LabelName(name)]))) + } + b.WriteByte('}') + return b.String() +} diff --git a/vendor/github.com/prometheus/common/model/labelset_string_go120.go b/vendor/github.com/prometheus/common/model/labelset_string_go120.go new file mode 100644 index 0000000..c421268 --- /dev/null +++ b/vendor/github.com/prometheus/common/model/labelset_string_go120.go @@ -0,0 +1,39 @@ +// Copyright 2024 The Prometheus Authors +// 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. + +//go:build !go1.21 + +package model + +import ( + "fmt" + "sort" + "strings" +) + +// String was optimized using functions not available for go 1.20 +// or lower. We keep the old implementation for compatibility with client_golang. +// Once client golang drops support for go 1.20 (scheduled for August 2024), this +// file can be removed. +func (l LabelSet) String() string { + labelNames := make([]string, 0, len(l)) + for name := range l { + labelNames = append(labelNames, string(name)) + } + sort.Strings(labelNames) + lstrs := make([]string, 0, len(l)) + for _, name := range labelNames { + lstrs = append(lstrs, fmt.Sprintf("%s=%q", name, l[LabelName(name)])) + } + return fmt.Sprintf("{%s}", strings.Join(lstrs, ", ")) +} diff --git a/vendor/github.com/prometheus/common/model/metric.go b/vendor/github.com/prometheus/common/model/metric.go index 0bd29b3..eb865e5 100644 --- a/vendor/github.com/prometheus/common/model/metric.go +++ b/vendor/github.com/prometheus/common/model/metric.go @@ -204,6 +204,7 @@ func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricF out := &dto.MetricFamily{ Help: v.Help, Type: v.Type, + Unit: v.Unit, } // If the name is nil, copy as-is, don't try to escape. diff --git a/vendor/github.com/prometheus/procfs/Makefile.common b/vendor/github.com/prometheus/procfs/Makefile.common index 062a281..9255815 100644 --- a/vendor/github.com/prometheus/procfs/Makefile.common +++ b/vendor/github.com/prometheus/procfs/Makefile.common @@ -61,11 +61,11 @@ PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_ SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= -GOLANGCI_LINT_VERSION ?= v1.54.2 -# golangci-lint only supports linux, darwin and windows platforms on i386/amd64. +GOLANGCI_LINT_VERSION ?= v1.55.2 +# golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) - ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386)) + ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386 arm64)) # If we're in CI and there is an Actions file, that means the linter # is being run in Actions, so we don't need to run it here. ifneq (,$(SKIP_GOLANGCI_LINT)) @@ -169,12 +169,16 @@ common-vet: common-lint: $(GOLANGCI_LINT) ifdef GOLANGCI_LINT @echo ">> running golangci-lint" -# 'go list' needs to be executed before staticcheck to prepopulate the modules cache. -# Otherwise staticcheck might fail randomly for some reason not yet explained. - $(GO) list -e -compiled -test=true -export=false -deps=true -find=false -tags= -- ./... > /dev/null $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) endif +.PHONY: common-lint-fix +common-lint-fix: $(GOLANGCI_LINT) +ifdef GOLANGCI_LINT + @echo ">> running golangci-lint fix" + $(GOLANGCI_LINT) run --fix $(GOLANGCI_LINT_OPTS) $(pkgs) +endif + .PHONY: common-yamllint common-yamllint: @echo ">> running yamllint on all YAML files in the repository" diff --git a/vendor/github.com/prometheus/procfs/crypto.go b/vendor/github.com/prometheus/procfs/crypto.go index 9a73e26..13ee66f 100644 --- a/vendor/github.com/prometheus/procfs/crypto.go +++ b/vendor/github.com/prometheus/procfs/crypto.go @@ -84,7 +84,7 @@ func parseCrypto(r io.Reader) ([]Crypto, error) { kv := strings.Split(text, ":") if len(kv) != 2 { - return nil, fmt.Errorf("%w: Cannot parae line: %q", ErrFileParse, text) + return nil, fmt.Errorf("%w: Cannot parse line: %q", ErrFileParse, text) } k := strings.TrimSpace(kv[0]) diff --git a/vendor/github.com/prometheus/procfs/meminfo.go b/vendor/github.com/prometheus/procfs/meminfo.go index eaf00e2..73a03e8 100644 --- a/vendor/github.com/prometheus/procfs/meminfo.go +++ b/vendor/github.com/prometheus/procfs/meminfo.go @@ -126,6 +126,7 @@ type Meminfo struct { VmallocUsed *uint64 // largest contiguous block of vmalloc area which is free VmallocChunk *uint64 + Percpu *uint64 HardwareCorrupted *uint64 AnonHugePages *uint64 ShmemHugePages *uint64 @@ -140,6 +141,55 @@ type Meminfo struct { DirectMap4k *uint64 DirectMap2M *uint64 DirectMap1G *uint64 + + // The struct fields below are the byte-normalized counterparts to the + // existing struct fields. Values are normalized using the optional + // unit field in the meminfo line. + MemTotalBytes *uint64 + MemFreeBytes *uint64 + MemAvailableBytes *uint64 + BuffersBytes *uint64 + CachedBytes *uint64 + SwapCachedBytes *uint64 + ActiveBytes *uint64 + InactiveBytes *uint64 + ActiveAnonBytes *uint64 + InactiveAnonBytes *uint64 + ActiveFileBytes *uint64 + InactiveFileBytes *uint64 + UnevictableBytes *uint64 + MlockedBytes *uint64 + SwapTotalBytes *uint64 + SwapFreeBytes *uint64 + DirtyBytes *uint64 + WritebackBytes *uint64 + AnonPagesBytes *uint64 + MappedBytes *uint64 + ShmemBytes *uint64 + SlabBytes *uint64 + SReclaimableBytes *uint64 + SUnreclaimBytes *uint64 + KernelStackBytes *uint64 + PageTablesBytes *uint64 + NFSUnstableBytes *uint64 + BounceBytes *uint64 + WritebackTmpBytes *uint64 + CommitLimitBytes *uint64 + CommittedASBytes *uint64 + VmallocTotalBytes *uint64 + VmallocUsedBytes *uint64 + VmallocChunkBytes *uint64 + PercpuBytes *uint64 + HardwareCorruptedBytes *uint64 + AnonHugePagesBytes *uint64 + ShmemHugePagesBytes *uint64 + ShmemPmdMappedBytes *uint64 + CmaTotalBytes *uint64 + CmaFreeBytes *uint64 + HugepagesizeBytes *uint64 + DirectMap4kBytes *uint64 + DirectMap2MBytes *uint64 + DirectMap1GBytes *uint64 } // Meminfo returns an information about current kernel/system memory statistics. @@ -162,114 +212,176 @@ func parseMemInfo(r io.Reader) (*Meminfo, error) { var m Meminfo s := bufio.NewScanner(r) for s.Scan() { - // Each line has at least a name and value; we ignore the unit. fields := strings.Fields(s.Text()) - if len(fields) < 2 { - return nil, fmt.Errorf("%w: Malformed line %q", ErrFileParse, s.Text()) - } + var val, valBytes uint64 - v, err := strconv.ParseUint(fields[1], 0, 64) + val, err := strconv.ParseUint(fields[1], 0, 64) if err != nil { return nil, err } + switch len(fields) { + case 2: + // No unit present, use the parsed the value as bytes directly. + valBytes = val + case 3: + // Unit present in optional 3rd field, convert it to + // bytes. The only unit supported within the Linux + // kernel is `kB`. + if fields[2] != "kB" { + return nil, fmt.Errorf("%w: Unsupported unit in optional 3rd field %q", ErrFileParse, fields[2]) + } + + valBytes = 1024 * val + + default: + return nil, fmt.Errorf("%w: Malformed line %q", ErrFileParse, s.Text()) + } + switch fields[0] { case "MemTotal:": - m.MemTotal = &v + m.MemTotal = &val + m.MemTotalBytes = &valBytes case "MemFree:": - m.MemFree = &v + m.MemFree = &val + m.MemFreeBytes = &valBytes case "MemAvailable:": - m.MemAvailable = &v + m.MemAvailable = &val + m.MemAvailableBytes = &valBytes case "Buffers:": - m.Buffers = &v + m.Buffers = &val + m.BuffersBytes = &valBytes case "Cached:": - m.Cached = &v + m.Cached = &val + m.CachedBytes = &valBytes case "SwapCached:": - m.SwapCached = &v + m.SwapCached = &val + m.SwapCachedBytes = &valBytes case "Active:": - m.Active = &v + m.Active = &val + m.ActiveBytes = &valBytes case "Inactive:": - m.Inactive = &v + m.Inactive = &val + m.InactiveBytes = &valBytes case "Active(anon):": - m.ActiveAnon = &v + m.ActiveAnon = &val + m.ActiveAnonBytes = &valBytes case "Inactive(anon):": - m.InactiveAnon = &v + m.InactiveAnon = &val + m.InactiveAnonBytes = &valBytes case "Active(file):": - m.ActiveFile = &v + m.ActiveFile = &val + m.ActiveFileBytes = &valBytes case "Inactive(file):": - m.InactiveFile = &v + m.InactiveFile = &val + m.InactiveFileBytes = &valBytes case "Unevictable:": - m.Unevictable = &v + m.Unevictable = &val + m.UnevictableBytes = &valBytes case "Mlocked:": - m.Mlocked = &v + m.Mlocked = &val + m.MlockedBytes = &valBytes case "SwapTotal:": - m.SwapTotal = &v + m.SwapTotal = &val + m.SwapTotalBytes = &valBytes case "SwapFree:": - m.SwapFree = &v + m.SwapFree = &val + m.SwapFreeBytes = &valBytes case "Dirty:": - m.Dirty = &v + m.Dirty = &val + m.DirtyBytes = &valBytes case "Writeback:": - m.Writeback = &v + m.Writeback = &val + m.WritebackBytes = &valBytes case "AnonPages:": - m.AnonPages = &v + m.AnonPages = &val + m.AnonPagesBytes = &valBytes case "Mapped:": - m.Mapped = &v + m.Mapped = &val + m.MappedBytes = &valBytes case "Shmem:": - m.Shmem = &v + m.Shmem = &val + m.ShmemBytes = &valBytes case "Slab:": - m.Slab = &v + m.Slab = &val + m.SlabBytes = &valBytes case "SReclaimable:": - m.SReclaimable = &v + m.SReclaimable = &val + m.SReclaimableBytes = &valBytes case "SUnreclaim:": - m.SUnreclaim = &v + m.SUnreclaim = &val + m.SUnreclaimBytes = &valBytes case "KernelStack:": - m.KernelStack = &v + m.KernelStack = &val + m.KernelStackBytes = &valBytes case "PageTables:": - m.PageTables = &v + m.PageTables = &val + m.PageTablesBytes = &valBytes case "NFS_Unstable:": - m.NFSUnstable = &v + m.NFSUnstable = &val + m.NFSUnstableBytes = &valBytes case "Bounce:": - m.Bounce = &v + m.Bounce = &val + m.BounceBytes = &valBytes case "WritebackTmp:": - m.WritebackTmp = &v + m.WritebackTmp = &val + m.WritebackTmpBytes = &valBytes case "CommitLimit:": - m.CommitLimit = &v + m.CommitLimit = &val + m.CommitLimitBytes = &valBytes case "Committed_AS:": - m.CommittedAS = &v + m.CommittedAS = &val + m.CommittedASBytes = &valBytes case "VmallocTotal:": - m.VmallocTotal = &v + m.VmallocTotal = &val + m.VmallocTotalBytes = &valBytes case "VmallocUsed:": - m.VmallocUsed = &v + m.VmallocUsed = &val + m.VmallocUsedBytes = &valBytes case "VmallocChunk:": - m.VmallocChunk = &v + m.VmallocChunk = &val + m.VmallocChunkBytes = &valBytes + case "Percpu:": + m.Percpu = &val + m.PercpuBytes = &valBytes case "HardwareCorrupted:": - m.HardwareCorrupted = &v + m.HardwareCorrupted = &val + m.HardwareCorruptedBytes = &valBytes case "AnonHugePages:": - m.AnonHugePages = &v + m.AnonHugePages = &val + m.AnonHugePagesBytes = &valBytes case "ShmemHugePages:": - m.ShmemHugePages = &v + m.ShmemHugePages = &val + m.ShmemHugePagesBytes = &valBytes case "ShmemPmdMapped:": - m.ShmemPmdMapped = &v + m.ShmemPmdMapped = &val + m.ShmemPmdMappedBytes = &valBytes case "CmaTotal:": - m.CmaTotal = &v + m.CmaTotal = &val + m.CmaTotalBytes = &valBytes case "CmaFree:": - m.CmaFree = &v + m.CmaFree = &val + m.CmaFreeBytes = &valBytes case "HugePages_Total:": - m.HugePagesTotal = &v + m.HugePagesTotal = &val case "HugePages_Free:": - m.HugePagesFree = &v + m.HugePagesFree = &val case "HugePages_Rsvd:": - m.HugePagesRsvd = &v + m.HugePagesRsvd = &val case "HugePages_Surp:": - m.HugePagesSurp = &v + m.HugePagesSurp = &val case "Hugepagesize:": - m.Hugepagesize = &v + m.Hugepagesize = &val + m.HugepagesizeBytes = &valBytes case "DirectMap4k:": - m.DirectMap4k = &v + m.DirectMap4k = &val + m.DirectMap4kBytes = &valBytes case "DirectMap2M:": - m.DirectMap2M = &v + m.DirectMap2M = &val + m.DirectMap2MBytes = &valBytes case "DirectMap1G:": - m.DirectMap1G = &v + m.DirectMap1G = &val + m.DirectMap1GBytes = &valBytes } } diff --git a/vendor/github.com/prometheus/procfs/net_ip_socket.go b/vendor/github.com/prometheus/procfs/net_ip_socket.go index 4da81ea..ba7d9ca 100644 --- a/vendor/github.com/prometheus/procfs/net_ip_socket.go +++ b/vendor/github.com/prometheus/procfs/net_ip_socket.go @@ -50,10 +50,13 @@ type ( // UsedSockets shows the total number of parsed lines representing the // number of used sockets. UsedSockets uint64 + // Drops shows the total number of dropped packets of all UPD sockets. + Drops *uint64 } // netIPSocketLine represents the fields parsed from a single line // in /proc/net/{t,u}dp{,6}. Fields which are not used by IPSocket are skipped. + // Drops is non-nil for udp{,6}, but nil for tcp{,6}. // For the proc file format details, see https://linux.die.net/man/5/proc. netIPSocketLine struct { Sl uint64 @@ -66,6 +69,7 @@ type ( RxQueue uint64 UID uint64 Inode uint64 + Drops *uint64 } ) @@ -77,13 +81,14 @@ func newNetIPSocket(file string) (NetIPSocket, error) { defer f.Close() var netIPSocket NetIPSocket + isUDP := strings.Contains(file, "udp") lr := io.LimitReader(f, readLimit) s := bufio.NewScanner(lr) s.Scan() // skip first line with headers for s.Scan() { fields := strings.Fields(s.Text()) - line, err := parseNetIPSocketLine(fields) + line, err := parseNetIPSocketLine(fields, isUDP) if err != nil { return nil, err } @@ -104,19 +109,25 @@ func newNetIPSocketSummary(file string) (*NetIPSocketSummary, error) { defer f.Close() var netIPSocketSummary NetIPSocketSummary + var udpPacketDrops uint64 + isUDP := strings.Contains(file, "udp") lr := io.LimitReader(f, readLimit) s := bufio.NewScanner(lr) s.Scan() // skip first line with headers for s.Scan() { fields := strings.Fields(s.Text()) - line, err := parseNetIPSocketLine(fields) + line, err := parseNetIPSocketLine(fields, isUDP) if err != nil { return nil, err } netIPSocketSummary.TxQueueLength += line.TxQueue netIPSocketSummary.RxQueueLength += line.RxQueue netIPSocketSummary.UsedSockets++ + if isUDP { + udpPacketDrops += *line.Drops + netIPSocketSummary.Drops = &udpPacketDrops + } } if err := s.Err(); err != nil { return nil, err @@ -149,7 +160,7 @@ func parseIP(hexIP string) (net.IP, error) { } // parseNetIPSocketLine parses a single line, represented by a list of fields. -func parseNetIPSocketLine(fields []string) (*netIPSocketLine, error) { +func parseNetIPSocketLine(fields []string, isUDP bool) (*netIPSocketLine, error) { line := &netIPSocketLine{} if len(fields) < 10 { return nil, fmt.Errorf( @@ -224,5 +235,14 @@ func parseNetIPSocketLine(fields []string) (*netIPSocketLine, error) { return nil, fmt.Errorf("%s: Cannot parse inode value in %q: %w", ErrFileParse, line.Inode, err) } + // drops + if isUDP { + drops, err := strconv.ParseUint(fields[12], 0, 64) + if err != nil { + return nil, fmt.Errorf("%s: Cannot parse drops value in %q: %w", ErrFileParse, drops, err) + } + line.Drops = &drops + } + return line, nil } diff --git a/vendor/github.com/prometheus/procfs/net_tls_stat.go b/vendor/github.com/prometheus/procfs/net_tls_stat.go new file mode 100644 index 0000000..13994c1 --- /dev/null +++ b/vendor/github.com/prometheus/procfs/net_tls_stat.go @@ -0,0 +1,119 @@ +// Copyright 2023 Prometheus Team +// 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 procfs + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +// TLSStat struct represents data in /proc/net/tls_stat. +// See https://docs.kernel.org/networking/tls.html#statistics +type TLSStat struct { + // number of TX sessions currently installed where host handles cryptography + TLSCurrTxSw int + // number of RX sessions currently installed where host handles cryptography + TLSCurrRxSw int + // number of TX sessions currently installed where NIC handles cryptography + TLSCurrTxDevice int + // number of RX sessions currently installed where NIC handles cryptography + TLSCurrRxDevice int + //number of TX sessions opened with host cryptography + TLSTxSw int + //number of RX sessions opened with host cryptography + TLSRxSw int + // number of TX sessions opened with NIC cryptography + TLSTxDevice int + // number of RX sessions opened with NIC cryptography + TLSRxDevice int + // record decryption failed (e.g. due to incorrect authentication tag) + TLSDecryptError int + // number of RX resyncs sent to NICs handling cryptography + TLSRxDeviceResync int + // number of RX records which had to be re-decrypted due to TLS_RX_EXPECT_NO_PAD mis-prediction. Note that this counter will also increment for non-data records. + TLSDecryptRetry int + // number of data RX records which had to be re-decrypted due to TLS_RX_EXPECT_NO_PAD mis-prediction. + TLSRxNoPadViolation int +} + +// NewTLSStat reads the tls_stat statistics. +func NewTLSStat() (TLSStat, error) { + fs, err := NewFS(DefaultMountPoint) + if err != nil { + return TLSStat{}, err + } + + return fs.NewTLSStat() +} + +// NewTLSStat reads the tls_stat statistics. +func (fs FS) NewTLSStat() (TLSStat, error) { + file, err := os.Open(fs.proc.Path("net/tls_stat")) + if err != nil { + return TLSStat{}, err + } + defer file.Close() + + var ( + tlsstat = TLSStat{} + s = bufio.NewScanner(file) + ) + + for s.Scan() { + fields := strings.Fields(s.Text()) + + if len(fields) != 2 { + return TLSStat{}, fmt.Errorf("%w: %q line %q", ErrFileParse, file.Name(), s.Text()) + } + + name := fields[0] + value, err := strconv.Atoi(fields[1]) + if err != nil { + return TLSStat{}, err + } + + switch name { + case "TlsCurrTxSw": + tlsstat.TLSCurrTxSw = value + case "TlsCurrRxSw": + tlsstat.TLSCurrRxSw = value + case "TlsCurrTxDevice": + tlsstat.TLSCurrTxDevice = value + case "TlsCurrRxDevice": + tlsstat.TLSCurrRxDevice = value + case "TlsTxSw": + tlsstat.TLSTxSw = value + case "TlsRxSw": + tlsstat.TLSRxSw = value + case "TlsTxDevice": + tlsstat.TLSTxDevice = value + case "TlsRxDevice": + tlsstat.TLSRxDevice = value + case "TlsDecryptError": + tlsstat.TLSDecryptError = value + case "TlsRxDeviceResync": + tlsstat.TLSRxDeviceResync = value + case "TlsDecryptRetry": + tlsstat.TLSDecryptRetry = value + case "TlsRxNoPadViolation": + tlsstat.TLSRxNoPadViolation = value + } + + } + + return tlsstat, s.Err() +} diff --git a/vendor/github.com/prometheus/procfs/proc_stat.go b/vendor/github.com/prometheus/procfs/proc_stat.go index 923e550..06a8d93 100644 --- a/vendor/github.com/prometheus/procfs/proc_stat.go +++ b/vendor/github.com/prometheus/procfs/proc_stat.go @@ -110,6 +110,11 @@ type ProcStat struct { Policy uint // Aggregated block I/O delays, measured in clock ticks (centiseconds). DelayAcctBlkIOTicks uint64 + // Guest time of the process (time spent running a virtual CPU for a guest + // operating system), measured in clock ticks. + GuestTime int + // Guest time of the process's children, measured in clock ticks. + CGuestTime int proc FS } @@ -189,6 +194,8 @@ func (p Proc) Stat() (ProcStat, error) { &s.RTPriority, &s.Policy, &s.DelayAcctBlkIOTicks, + &s.GuestTime, + &s.CGuestTime, ) if err != nil { return ProcStat{}, err diff --git a/vendor/github.com/twmb/murmur3/.gitignore b/vendor/github.com/twmb/murmur3/.gitignore new file mode 100644 index 0000000..1377554 --- /dev/null +++ b/vendor/github.com/twmb/murmur3/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/vendor/github.com/twmb/murmur3/LICENSE b/vendor/github.com/twmb/murmur3/LICENSE new file mode 100644 index 0000000..e4a085c --- /dev/null +++ b/vendor/github.com/twmb/murmur3/LICENSE @@ -0,0 +1,49 @@ +Copyright 2013, Sébastien Paolacci. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the library nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Copyright 2018, Travis Bischel. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the library nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/twmb/murmur3/README.md b/vendor/github.com/twmb/murmur3/README.md new file mode 100644 index 0000000..1917fe3 --- /dev/null +++ b/vendor/github.com/twmb/murmur3/README.md @@ -0,0 +1,129 @@ +murmur3 +======= + +Native Go implementation of Austin Appleby's third MurmurHash revision (aka +MurmurHash3). + +Includes assembly for amd64 for 64/128 bit hashes, seeding functions, +and string functions to avoid string to slice conversions. + +Hand rolled 32 bit assembly was removed during 1.11, but may be reintroduced +if the compiler slows down any more. As is, the compiler generates marginally +slower code (by one instruction in the hot loop). + +The reference algorithm has been slightly hacked as to support the streaming mode +required by Go's standard [Hash interface](http://golang.org/pkg/hash/#Hash). + +Endianness +========== + +Unlike the canonical source, this library **always** reads bytes as little +endian numbers. This makes the hashes portable across architectures, although +does mean that hashing is a bit slower on big endian architectures. + +Safety +====== + +This library used to use `unsafe` to convert four bytes to a `uint32` and eight +bytes to a `uint64`, but Go 1.14 introduced checks around those types of +conversions that flagged that code as erroneous when hashing on unaligned +input. While the code would not be problematic on amd64, it could be +problematic on some architectures. + +As of Go 1.14, those conversions were removed at the expense of a very minor +performance hit. This hit affects all cpu architectures on for `Sum32`, and +non-amd64 architectures for `Sum64` and `Sum128`. For 64 and 128, custom +assembly exists for amd64 that preserves performance. + +Testing +======= + +[![Build Status](https://travis-ci.org/twmb/murmur3.svg?branch=master)](https://travis-ci.org/twmb/murmur3) + +Testing includes comparing random inputs against the [canonical +implementation](https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp), +and testing length 0 through 17 inputs to force all branches. + +Because this code always reads input as little endian, testing against the +canonical source is skipped for big endian architectures. The canonical source +just converts bytes to numbers, meaning on big endian architectures, it will +use different numbers for its hashing. + +Documentation +============= + +[![GoDoc](https://godoc.org/github.com/twmb/murmur3?status.svg)](https://godoc.org/github.com/twmb/murmur3) + +Full documentation can be found on `godoc`. + +Benchmarks +========== + +Benchmarks below were run on an amd64 machine with _and_ without the custom +assembly. The following numbers are for Go 1.14.1 and are comparing against +[spaolacci/murmur3](https://github.com/spaolacci/murmur3). + +You will notice that at small sizes, the other library is better. This is due +to this library converting to safe code for Go 1.14. At large sizes, this +library is nearly identical to the other. On amd64, the 64 bit and 128 bit +sums come out to ~9% faster. + +32 bit sums: + +``` +32Sizes/32-12 3.00GB/s ± 1% 2.12GB/s ±11% -29.24% (p=0.000 n=9+10) +32Sizes/64-12 3.61GB/s ± 3% 2.79GB/s ± 8% -22.62% (p=0.000 n=10+10) +32Sizes/128-12 3.47GB/s ± 8% 2.79GB/s ± 4% -19.47% (p=0.000 n=10+10) +32Sizes/256-12 3.66GB/s ± 4% 3.25GB/s ± 6% -11.09% (p=0.000 n=10+10) +32Sizes/512-12 3.78GB/s ± 3% 3.54GB/s ± 4% -6.30% (p=0.000 n=9+9) +32Sizes/1024-12 3.86GB/s ± 3% 3.69GB/s ± 5% -4.46% (p=0.000 n=10+10) +32Sizes/2048-12 3.85GB/s ± 3% 3.81GB/s ± 3% ~ (p=0.079 n=10+9) +32Sizes/4096-12 3.90GB/s ± 3% 3.82GB/s ± 2% -2.14% (p=0.029 n=10+10) +32Sizes/8192-12 3.82GB/s ± 3% 3.78GB/s ± 7% ~ (p=0.529 n=10+10) +``` + +64/128 bit sums, non-amd64: + +``` +64Sizes/32-12 2.34GB/s ± 5% 2.64GB/s ± 9% +12.87% (p=0.000 n=10+10) +64Sizes/64-12 3.62GB/s ± 5% 3.96GB/s ± 4% +9.41% (p=0.000 n=10+10) +64Sizes/128-12 5.12GB/s ± 3% 5.44GB/s ± 4% +6.09% (p=0.000 n=10+9) +64Sizes/256-12 6.35GB/s ± 2% 6.27GB/s ± 9% ~ (p=0.796 n=10+10) +64Sizes/512-12 6.58GB/s ± 7% 6.79GB/s ± 3% ~ (p=0.075 n=10+10) +64Sizes/1024-12 7.49GB/s ± 3% 7.55GB/s ± 9% ~ (p=0.393 n=10+10) +64Sizes/2048-12 8.06GB/s ± 2% 7.90GB/s ± 6% ~ (p=0.156 n=9+10) +64Sizes/4096-12 8.27GB/s ± 6% 8.22GB/s ± 5% ~ (p=0.631 n=10+10) +64Sizes/8192-12 8.35GB/s ± 4% 8.38GB/s ± 6% ~ (p=0.631 n=10+10) +128Sizes/32-12 2.27GB/s ± 2% 2.68GB/s ± 5% +18.00% (p=0.000 n=10+10) +128Sizes/64-12 3.55GB/s ± 2% 4.00GB/s ± 3% +12.47% (p=0.000 n=8+9) +128Sizes/128-12 5.09GB/s ± 1% 5.43GB/s ± 3% +6.65% (p=0.000 n=9+9) +128Sizes/256-12 6.33GB/s ± 3% 5.65GB/s ± 4% -10.79% (p=0.000 n=9+10) +128Sizes/512-12 6.78GB/s ± 3% 6.74GB/s ± 6% ~ (p=0.968 n=9+10) +128Sizes/1024-12 7.46GB/s ± 4% 7.56GB/s ± 4% ~ (p=0.222 n=9+9) +128Sizes/2048-12 7.99GB/s ± 4% 7.96GB/s ± 3% ~ (p=0.666 n=9+9) +128Sizes/4096-12 8.20GB/s ± 2% 8.25GB/s ± 4% ~ (p=0.631 n=10+10) +128Sizes/8192-12 8.24GB/s ± 2% 8.26GB/s ± 5% ~ (p=0.673 n=8+9) +``` + +64/128 bit sums, amd64: + +``` +64Sizes/32-12 2.34GB/s ± 5% 4.36GB/s ± 3% +85.86% (p=0.000 n=10+10) +64Sizes/64-12 3.62GB/s ± 5% 6.27GB/s ± 3% +73.37% (p=0.000 n=10+9) +64Sizes/128-12 5.12GB/s ± 3% 7.70GB/s ± 6% +50.27% (p=0.000 n=10+10) +64Sizes/256-12 6.35GB/s ± 2% 8.61GB/s ± 3% +35.50% (p=0.000 n=10+10) +64Sizes/512-12 6.58GB/s ± 7% 8.59GB/s ± 4% +30.48% (p=0.000 n=10+9) +64Sizes/1024-12 7.49GB/s ± 3% 8.81GB/s ± 2% +17.66% (p=0.000 n=10+10) +64Sizes/2048-12 8.06GB/s ± 2% 8.90GB/s ± 4% +10.49% (p=0.000 n=9+10) +64Sizes/4096-12 8.27GB/s ± 6% 8.90GB/s ± 4% +7.54% (p=0.000 n=10+10) +64Sizes/8192-12 8.35GB/s ± 4% 9.00GB/s ± 3% +7.80% (p=0.000 n=10+9) +128Sizes/32-12 2.27GB/s ± 2% 4.29GB/s ± 9% +88.75% (p=0.000 n=10+10) +128Sizes/64-12 3.55GB/s ± 2% 6.10GB/s ± 8% +71.78% (p=0.000 n=8+10) +128Sizes/128-12 5.09GB/s ± 1% 7.62GB/s ± 9% +49.63% (p=0.000 n=9+10) +128Sizes/256-12 6.33GB/s ± 3% 8.65GB/s ± 3% +36.71% (p=0.000 n=9+10) +128Sizes/512-12 6.78GB/s ± 3% 8.39GB/s ± 6% +23.77% (p=0.000 n=9+10) +128Sizes/1024-12 7.46GB/s ± 4% 8.70GB/s ± 4% +16.70% (p=0.000 n=9+10) +128Sizes/2048-12 7.99GB/s ± 4% 8.73GB/s ± 8% +9.26% (p=0.003 n=9+10) +128Sizes/4096-12 8.20GB/s ± 2% 8.86GB/s ± 6% +8.00% (p=0.000 n=10+10) +128Sizes/8192-12 8.24GB/s ± 2% 9.01GB/s ± 3% +9.30% (p=0.000 n=8+10) +``` diff --git a/vendor/github.com/twmb/murmur3/murmur.go b/vendor/github.com/twmb/murmur3/murmur.go new file mode 100644 index 0000000..84f9057 --- /dev/null +++ b/vendor/github.com/twmb/murmur3/murmur.go @@ -0,0 +1,58 @@ +// Copyright 2013, Sébastien Paolacci. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package murmur3 provides an amd64 native (Go generic fallback) +// implementation of the murmur3 hash algorithm for strings and slices. +// +// Assembly is provided for amd64 go1.5+; pull requests are welcome for other +// architectures. +package murmur3 + +type bmixer interface { + bmix(p []byte) (tail []byte) + Size() (n int) + reset() +} + +type digest struct { + clen int // Digested input cumulative length. + tail []byte // 0 to Size()-1 bytes view of `buf'. + buf [16]byte // Expected (but not required) to be Size() large. + bmixer +} + +func (d *digest) BlockSize() int { return 1 } + +func (d *digest) Write(p []byte) (n int, err error) { + n = len(p) + d.clen += n + + if len(d.tail) > 0 { + // Stick back pending bytes. + nfree := d.Size() - len(d.tail) // nfree ∈ [1, d.Size()-1]. + if nfree < len(p) { + // One full block can be formed. + block := append(d.tail, p[:nfree]...) + p = p[nfree:] + _ = d.bmix(block) // No tail. + } else { + // Tail's buf is large enough to prevent reallocs. + p = append(d.tail, p...) + } + } + + d.tail = d.bmix(p) + + // Keep own copy of the 0 to Size()-1 pending bytes. + nn := copy(d.buf[:], d.tail) + d.tail = d.buf[:nn] + + return n, nil +} + +func (d *digest) Reset() { + d.clen = 0 + d.tail = nil + d.bmixer.reset() +} diff --git a/vendor/github.com/twmb/murmur3/murmur128.go b/vendor/github.com/twmb/murmur3/murmur128.go new file mode 100644 index 0000000..d02199f --- /dev/null +++ b/vendor/github.com/twmb/murmur3/murmur128.go @@ -0,0 +1,182 @@ +package murmur3 + +import ( + "hash" + "math/bits" +) + +const ( + c1_128 = 0x87c37b91114253d5 + c2_128 = 0x4cf5ad432745937f +) + +// Make sure interfaces are correctly implemented. +var ( + _ hash.Hash = new(digest128) + _ Hash128 = new(digest128) + _ bmixer = new(digest128) +) + +// Hash128 provides an interface for a streaming 128 bit hash. +type Hash128 interface { + hash.Hash + Sum128() (uint64, uint64) +} + +// digest128 represents a partial evaluation of a 128 bites hash. +type digest128 struct { + digest + seed1 uint64 + seed2 uint64 + h1 uint64 // Unfinalized running hash part 1. + h2 uint64 // Unfinalized running hash part 2. +} + +// SeedNew128 returns a Hash128 for streaming 128 bit sums with its internal +// digests initialized to seed1 and seed2. +// +// The canonical implementation allows one only uint32 seed; to imitate that +// behavior, use the same, uint32-max seed for seed1 and seed2. +func SeedNew128(seed1, seed2 uint64) Hash128 { + d := &digest128{seed1: seed1, seed2: seed2} + d.bmixer = d + d.Reset() + return d +} + +// New128 returns a Hash128 for streaming 128 bit sums. +func New128() Hash128 { + return SeedNew128(0, 0) +} + +func (d *digest128) Size() int { return 16 } + +func (d *digest128) reset() { d.h1, d.h2 = d.seed1, d.seed2 } + +func (d *digest128) Sum(b []byte) []byte { + h1, h2 := d.Sum128() + return append(b, + byte(h1>>56), byte(h1>>48), byte(h1>>40), byte(h1>>32), + byte(h1>>24), byte(h1>>16), byte(h1>>8), byte(h1), + + byte(h2>>56), byte(h2>>48), byte(h2>>40), byte(h2>>32), + byte(h2>>24), byte(h2>>16), byte(h2>>8), byte(h2), + ) +} + +func (d *digest128) bmix(p []byte) (tail []byte) { + h1, h2 := d.h1, d.h2 + + for len(p) >= 16 { + k1 := uint64(p[0]) | uint64(p[1])<<8 | uint64(p[2])<<16 | uint64(p[3])<<24 | uint64(p[4])<<32 | uint64(p[5])<<40 | uint64(p[6])<<48 | uint64(p[7])<<56 + k2 := uint64(p[8]) | uint64(p[9])<<8 | uint64(p[10])<<16 | uint64(p[11])<<24 | uint64(p[12])<<32 | uint64(p[13])<<40 | uint64(p[14])<<48 | uint64(p[15])<<56 + p = p[16:] + + k1 *= c1_128 + k1 = bits.RotateLeft64(k1, 31) + k1 *= c2_128 + h1 ^= k1 + + h1 = bits.RotateLeft64(h1, 27) + h1 += h2 + h1 = h1*5 + 0x52dce729 + + k2 *= c2_128 + k2 = bits.RotateLeft64(k2, 33) + k2 *= c1_128 + h2 ^= k2 + + h2 = bits.RotateLeft64(h2, 31) + h2 += h1 + h2 = h2*5 + 0x38495ab5 + } + d.h1, d.h2 = h1, h2 + return p +} + +func (d *digest128) Sum128() (h1, h2 uint64) { + + h1, h2 = d.h1, d.h2 + + var k1, k2 uint64 + switch len(d.tail) & 15 { + case 15: + k2 ^= uint64(d.tail[14]) << 48 + fallthrough + case 14: + k2 ^= uint64(d.tail[13]) << 40 + fallthrough + case 13: + k2 ^= uint64(d.tail[12]) << 32 + fallthrough + case 12: + k2 ^= uint64(d.tail[11]) << 24 + fallthrough + case 11: + k2 ^= uint64(d.tail[10]) << 16 + fallthrough + case 10: + k2 ^= uint64(d.tail[9]) << 8 + fallthrough + case 9: + k2 ^= uint64(d.tail[8]) << 0 + + k2 *= c2_128 + k2 = bits.RotateLeft64(k2, 33) + k2 *= c1_128 + h2 ^= k2 + + fallthrough + + case 8: + k1 ^= uint64(d.tail[7]) << 56 + fallthrough + case 7: + k1 ^= uint64(d.tail[6]) << 48 + fallthrough + case 6: + k1 ^= uint64(d.tail[5]) << 40 + fallthrough + case 5: + k1 ^= uint64(d.tail[4]) << 32 + fallthrough + case 4: + k1 ^= uint64(d.tail[3]) << 24 + fallthrough + case 3: + k1 ^= uint64(d.tail[2]) << 16 + fallthrough + case 2: + k1 ^= uint64(d.tail[1]) << 8 + fallthrough + case 1: + k1 ^= uint64(d.tail[0]) << 0 + k1 *= c1_128 + k1 = bits.RotateLeft64(k1, 31) + k1 *= c2_128 + h1 ^= k1 + } + + h1 ^= uint64(d.clen) + h2 ^= uint64(d.clen) + + h1 += h2 + h2 += h1 + + h1 = fmix64(h1) + h2 = fmix64(h2) + + h1 += h2 + h2 += h1 + + return h1, h2 +} + +func fmix64(k uint64) uint64 { + k ^= k >> 33 + k *= 0xff51afd7ed558ccd + k ^= k >> 33 + k *= 0xc4ceb9fe1a85ec53 + k ^= k >> 33 + return k +} diff --git a/vendor/github.com/twmb/murmur3/murmur128_amd64.s b/vendor/github.com/twmb/murmur3/murmur128_amd64.s new file mode 100644 index 0000000..6342807 --- /dev/null +++ b/vendor/github.com/twmb/murmur3/murmur128_amd64.s @@ -0,0 +1,247 @@ +//go:build go1.5 && amd64 && !gccgo +// +build go1.5,amd64,!gccgo + +// SeedSum128(seed1, seed2 uint64, data []byte) (h1 uint64, h2 uint64) +TEXT ·SeedSum128(SB), $0-56 + MOVQ seed1+0(FP), R12 + MOVQ seed2+8(FP), R13 + MOVQ data_base+16(FP), SI + MOVQ data_len+24(FP), R9 + LEAQ h1+40(FP), BX + JMP sum128internal<>(SB) + +// Sum128(data []byte) (h1 uint64, h2 uint64) +TEXT ·Sum128(SB), $0-40 + XORQ R12, R12 + XORQ R13, R13 + MOVQ data_base+0(FP), SI + MOVQ data_len+8(FP), R9 + LEAQ h1+24(FP), BX + JMP sum128internal<>(SB) + +// SeedStringSum128(seed1, seed2 uint64, data string) (h1 uint64, h2 uint64) +TEXT ·SeedStringSum128(SB), $0-48 + MOVQ seed1+0(FP), R12 + MOVQ seed2+8(FP), R13 + MOVQ data_base+16(FP), SI + MOVQ data_len+24(FP), R9 + LEAQ h1+32(FP), BX + JMP sum128internal<>(SB) + +// StringSum128(data string) (h1 uint64, h2 uint64) +TEXT ·StringSum128(SB), $0-32 + XORQ R12, R12 + XORQ R13, R13 + MOVQ data_base+0(FP), SI + MOVQ data_len+8(FP), R9 + LEAQ h1+16(FP), BX + JMP sum128internal<>(SB) + +// Expects: +// R12 == h1 uint64 seed +// R13 == h2 uint64 seed +// SI == &data +// R9 == len(data) +// BX == &[2]uint64 return +TEXT sum128internal<>(SB), $0 + MOVQ $0x87c37b91114253d5, R14 // c1 + MOVQ $0x4cf5ad432745937f, R15 // c2 + + MOVQ R9, CX + ANDQ $-16, CX // cx == data_len - (data_len % 16) + + // for r10 = 0; r10 < cx; r10 += 16 {... + XORQ R10, R10 + +loop: + CMPQ R10, CX + JE tail + MOVQ (SI)(R10*1), AX + MOVQ 8(SI)(R10*1), DX + ADDQ $16, R10 + + IMULQ R14, AX + IMULQ R15, DX + + ROLQ $31, AX + ROLQ $33, DX + + IMULQ R15, AX + IMULQ R14, DX + + XORQ AX, R12 + ROLQ $27, R12 + ADDQ R13, R12 + XORQ DX, R13 + ROLQ $31, R13 + LEAQ 0x52dce729(R12)(R12*4), R12 + + ADDQ R12, R13 + LEAQ 0x38495ab5(R13)(R13*4), R13 + + JMP loop + +tail: + MOVQ R9, CX + ANDQ $0xf, CX + JZ finalize // if len % 16 == 0 + + XORQ AX, AX + + // poor man's binary tree jump table + SUBQ $8, CX + JZ tail8 + JG over8 + ADDQ $4, CX + JZ tail4 + JG over4 + ADDQ $2, CX + JL tail1 + JZ tail2 + JMP tail3 + +over4: + SUBQ $2, CX + JL tail5 + JZ tail6 + JMP tail7 + +over8: + SUBQ $4, CX + JZ tail12 + JG over12 + ADDQ $2, CX + JL tail9 + JZ tail10 + JMP tail11 + +over12: + SUBQ $2, CX + JL tail13 + JZ tail14 + +tail15: + MOVBQZX 14(SI)(R10*1), AX + SALQ $16, AX + +tail14: + MOVW 12(SI)(R10*1), AX + SALQ $32, AX + JMP tail12 + +tail13: + MOVBQZX 12(SI)(R10*1), AX + SALQ $32, AX + +tail12: + MOVL 8(SI)(R10*1), DX + ORQ DX, AX + JMP fintailhigh + +tail11: + MOVBQZX 10(SI)(R10*1), AX + SALQ $16, AX + +tail10: + MOVW 8(SI)(R10*1), AX + JMP fintailhigh + +tail9: + MOVB 8(SI)(R10*1), AL + +fintailhigh: + IMULQ R15, AX + ROLQ $33, AX + IMULQ R14, AX + XORQ AX, R13 + +tail8: + MOVQ (SI)(R10*1), AX + JMP fintaillow + +tail7: + MOVBQZX 6(SI)(R10*1), AX + SALQ $16, AX + +tail6: + MOVW 4(SI)(R10*1), AX + SALQ $32, AX + JMP tail4 + +tail5: + MOVBQZX 4(SI)(R10*1), AX + SALQ $32, AX + +tail4: + MOVL (SI)(R10*1), DX + ORQ DX, AX + JMP fintaillow + +tail3: + MOVBQZX 2(SI)(R10*1), AX + SALQ $16, AX + +tail2: + MOVW (SI)(R10*1), AX + JMP fintaillow + +tail1: + MOVB (SI)(R10*1), AL + +fintaillow: + IMULQ R14, AX + ROLQ $31, AX + IMULQ R15, AX + XORQ AX, R12 + +finalize: + XORQ R9, R12 + XORQ R9, R13 + + ADDQ R13, R12 + ADDQ R12, R13 + + // fmix128 (both interleaved) + MOVQ R12, DX + MOVQ R13, AX + + SHRQ $33, DX + SHRQ $33, AX + + XORQ DX, R12 + XORQ AX, R13 + + MOVQ $0xff51afd7ed558ccd, CX + + IMULQ CX, R12 + IMULQ CX, R13 + + MOVQ R12, DX + MOVQ R13, AX + + SHRQ $33, DX + SHRQ $33, AX + + XORQ DX, R12 + XORQ AX, R13 + + MOVQ $0xc4ceb9fe1a85ec53, CX + + IMULQ CX, R12 + IMULQ CX, R13 + + MOVQ R12, DX + MOVQ R13, AX + + SHRQ $33, DX + SHRQ $33, AX + + XORQ DX, R12 + XORQ AX, R13 + + ADDQ R13, R12 + ADDQ R12, R13 + + MOVQ R12, (BX) + MOVQ R13, 8(BX) + RET diff --git a/vendor/github.com/twmb/murmur3/murmur128_decl.go b/vendor/github.com/twmb/murmur3/murmur128_decl.go new file mode 100644 index 0000000..afc601e --- /dev/null +++ b/vendor/github.com/twmb/murmur3/murmur128_decl.go @@ -0,0 +1,36 @@ +//go:build go1.5 && amd64 && !gccgo +// +build go1.5,amd64,!gccgo + +package murmur3 + +//go:noescape + +// Sum128 returns the murmur3 sum of data. It is equivalent to the following +// sequence (without the extra burden and the extra allocation): +// +// hasher := New128() +// hasher.Write(data) +// return hasher.Sum128() +func Sum128(data []byte) (h1 uint64, h2 uint64) + +//go:noescape + +// SeedSum128 returns the murmur3 sum of data with digests initialized to seed1 +// and seed2. +// +// The canonical implementation allows only one uint32 seed; to imitate that +// behavior, use the same, uint32-max seed for seed1 and seed2. +// +// This reads and processes the data in chunks of little endian uint64s; +// thus, the returned hashes are portable across architectures. +func SeedSum128(seed1, seed2 uint64, data []byte) (h1 uint64, h2 uint64) + +//go:noescape + +// StringSum128 is the string version of Sum128. +func StringSum128(data string) (h1 uint64, h2 uint64) + +//go:noescape + +// SeedStringSum128 is the string version of SeedSum128. +func SeedStringSum128(seed1, seed2 uint64, data string) (h1 uint64, h2 uint64) diff --git a/vendor/github.com/twmb/murmur3/murmur128_gen.go b/vendor/github.com/twmb/murmur3/murmur128_gen.go new file mode 100644 index 0000000..8b482fe --- /dev/null +++ b/vendor/github.com/twmb/murmur3/murmur128_gen.go @@ -0,0 +1,236 @@ +//go:build !go1.5 || !amd64 || gccgo +// +build !go1.5 !amd64 gccgo + +package murmur3 + +import "math/bits" + +// Sum128 returns the murmur3 sum of data. It is equivalent to the following +// sequence (without the extra burden and the extra allocation): +// +// hasher := New128() +// hasher.Write(data) +// return hasher.Sum128() +func Sum128(data []byte) (h1 uint64, h2 uint64) { + return SeedSum128(0, 0, data) +} + +// SeedSum128 returns the murmur3 sum of data with digests initialized to seed1 +// and seed2. +// +// The canonical implementation allows only one uint32 seed; to imitate that +// behavior, use the same, uint32-max seed for seed1 and seed2. +// +// This reads and processes the data in chunks of little endian uint64s; +// thus, the returned hashes are portable across architectures. +func SeedSum128(seed1, seed2 uint64, data []byte) (h1 uint64, h2 uint64) { + h1, h2 = seed1, seed2 + clen := len(data) + for len(data) >= 16 { + // yes, this is faster than using binary.LittleEndian.Uint64 + k1 := uint64(data[0]) | uint64(data[1])<<8 | uint64(data[2])<<16 | uint64(data[3])<<24 | uint64(data[4])<<32 | uint64(data[5])<<40 | uint64(data[6])<<48 | uint64(data[7])<<56 + k2 := uint64(data[8]) | uint64(data[9])<<8 | uint64(data[10])<<16 | uint64(data[11])<<24 | uint64(data[12])<<32 | uint64(data[13])<<40 | uint64(data[14])<<48 | uint64(data[15])<<56 + + data = data[16:] + + k1 *= c1_128 + k1 = bits.RotateLeft64(k1, 31) + k1 *= c2_128 + h1 ^= k1 + + h1 = bits.RotateLeft64(h1, 27) + h1 += h2 + h1 = h1*5 + 0x52dce729 + + k2 *= c2_128 + k2 = bits.RotateLeft64(k2, 33) + k2 *= c1_128 + h2 ^= k2 + + h2 = bits.RotateLeft64(h2, 31) + h2 += h1 + h2 = h2*5 + 0x38495ab5 + } + + var k1, k2 uint64 + switch len(data) { + case 15: + k2 ^= uint64(data[14]) << 48 + fallthrough + case 14: + k2 ^= uint64(data[13]) << 40 + fallthrough + case 13: + k2 ^= uint64(data[12]) << 32 + fallthrough + case 12: + k2 ^= uint64(data[11]) << 24 + fallthrough + case 11: + k2 ^= uint64(data[10]) << 16 + fallthrough + case 10: + k2 ^= uint64(data[9]) << 8 + fallthrough + case 9: + k2 ^= uint64(data[8]) << 0 + + k2 *= c2_128 + k2 = bits.RotateLeft64(k2, 33) + k2 *= c1_128 + h2 ^= k2 + + fallthrough + + case 8: + k1 ^= uint64(data[7]) << 56 + fallthrough + case 7: + k1 ^= uint64(data[6]) << 48 + fallthrough + case 6: + k1 ^= uint64(data[5]) << 40 + fallthrough + case 5: + k1 ^= uint64(data[4]) << 32 + fallthrough + case 4: + k1 ^= uint64(data[3]) << 24 + fallthrough + case 3: + k1 ^= uint64(data[2]) << 16 + fallthrough + case 2: + k1 ^= uint64(data[1]) << 8 + fallthrough + case 1: + k1 ^= uint64(data[0]) << 0 + k1 *= c1_128 + k1 = bits.RotateLeft64(k1, 31) + k1 *= c2_128 + h1 ^= k1 + } + + h1 ^= uint64(clen) + h2 ^= uint64(clen) + + h1 += h2 + h2 += h1 + + h1 = fmix64(h1) + h2 = fmix64(h2) + + h1 += h2 + h2 += h1 + + return h1, h2 +} + +// StringSum128 is the string version of Sum128. +func StringSum128(data string) (h1 uint64, h2 uint64) { + return SeedStringSum128(0, 0, data) +} + +// SeedStringSum128 is the string version of SeedSum128. +func SeedStringSum128(seed1, seed2 uint64, data string) (h1 uint64, h2 uint64) { + h1, h2 = seed1, seed2 + clen := len(data) + for len(data) >= 16 { + // yes, this is faster than using binary.LittleEndian.Uint64 + k1 := uint64(data[0]) | uint64(data[1])<<8 | uint64(data[2])<<16 | uint64(data[3])<<24 | uint64(data[4])<<32 | uint64(data[5])<<40 | uint64(data[6])<<48 | uint64(data[7])<<56 + k2 := uint64(data[8]) | uint64(data[9])<<8 | uint64(data[10])<<16 | uint64(data[11])<<24 | uint64(data[12])<<32 | uint64(data[13])<<40 | uint64(data[14])<<48 | uint64(data[15])<<56 + + data = data[16:] + + k1 *= c1_128 + k1 = bits.RotateLeft64(k1, 31) + k1 *= c2_128 + h1 ^= k1 + + h1 = bits.RotateLeft64(h1, 27) + h1 += h2 + h1 = h1*5 + 0x52dce729 + + k2 *= c2_128 + k2 = bits.RotateLeft64(k2, 33) + k2 *= c1_128 + h2 ^= k2 + + h2 = bits.RotateLeft64(h2, 31) + h2 += h1 + h2 = h2*5 + 0x38495ab5 + } + + var k1, k2 uint64 + switch len(data) { + case 15: + k2 ^= uint64(data[14]) << 48 + fallthrough + case 14: + k2 ^= uint64(data[13]) << 40 + fallthrough + case 13: + k2 ^= uint64(data[12]) << 32 + fallthrough + case 12: + k2 ^= uint64(data[11]) << 24 + fallthrough + case 11: + k2 ^= uint64(data[10]) << 16 + fallthrough + case 10: + k2 ^= uint64(data[9]) << 8 + fallthrough + case 9: + k2 ^= uint64(data[8]) << 0 + + k2 *= c2_128 + k2 = bits.RotateLeft64(k2, 33) + k2 *= c1_128 + h2 ^= k2 + + fallthrough + + case 8: + k1 ^= uint64(data[7]) << 56 + fallthrough + case 7: + k1 ^= uint64(data[6]) << 48 + fallthrough + case 6: + k1 ^= uint64(data[5]) << 40 + fallthrough + case 5: + k1 ^= uint64(data[4]) << 32 + fallthrough + case 4: + k1 ^= uint64(data[3]) << 24 + fallthrough + case 3: + k1 ^= uint64(data[2]) << 16 + fallthrough + case 2: + k1 ^= uint64(data[1]) << 8 + fallthrough + case 1: + k1 ^= uint64(data[0]) << 0 + k1 *= c1_128 + k1 = bits.RotateLeft64(k1, 31) + k1 *= c2_128 + h1 ^= k1 + } + + h1 ^= uint64(clen) + h2 ^= uint64(clen) + + h1 += h2 + h2 += h1 + + h1 = fmix64(h1) + h2 = fmix64(h2) + + h1 += h2 + h2 += h1 + + return h1, h2 +} diff --git a/vendor/github.com/twmb/murmur3/murmur32.go b/vendor/github.com/twmb/murmur3/murmur32.go new file mode 100644 index 0000000..f61b58f --- /dev/null +++ b/vendor/github.com/twmb/murmur3/murmur32.go @@ -0,0 +1,100 @@ +package murmur3 + +import ( + "hash" + "math/bits" +) + +// Make sure interfaces are correctly implemented. +var ( + _ hash.Hash = new(digest32) + _ hash.Hash32 = new(digest32) +) + +const ( + c1_32 uint32 = 0xcc9e2d51 + c2_32 uint32 = 0x1b873593 +) + +// digest32 represents a partial evaluation of a 32 bites hash. +type digest32 struct { + digest + seed uint32 + h1 uint32 // Unfinalized running hash. +} + +// SeedNew32 returns a hash.Hash32 for streaming 32 bit sums with its internal +// digest initialized to seed. +// +// This reads and processes the data in chunks of little endian uint32s; +// thus, the returned hash is portable across architectures. +func SeedNew32(seed uint32) hash.Hash32 { + d := &digest32{seed: seed} + d.bmixer = d + d.Reset() + return d +} + +// New32 returns a hash.Hash32 for streaming 32 bit sums. +func New32() hash.Hash32 { + return SeedNew32(0) +} + +func (d *digest32) Size() int { return 4 } + +func (d *digest32) reset() { d.h1 = d.seed } + +func (d *digest32) Sum(b []byte) []byte { + h := d.Sum32() + return append(b, byte(h>>24), byte(h>>16), byte(h>>8), byte(h)) +} + +// Digest as many blocks as possible. +func (d *digest32) bmix(p []byte) (tail []byte) { + h1 := d.h1 + + for len(p) >= 4 { + k1 := uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24 + p = p[4:] + + k1 *= c1_32 + k1 = bits.RotateLeft32(k1, 15) + k1 *= c2_32 + + h1 ^= k1 + h1 = bits.RotateLeft32(h1, 13) + h1 = h1*5 + 0xe6546b64 + } + d.h1 = h1 + return p +} + +func (d *digest32) Sum32() (h1 uint32) { + + h1 = d.h1 + var k1 uint32 + switch len(d.tail) & 3 { + case 3: + k1 ^= uint32(d.tail[2]) << 16 + fallthrough + case 2: + k1 ^= uint32(d.tail[1]) << 8 + fallthrough + case 1: + k1 ^= uint32(d.tail[0]) + k1 *= c1_32 + k1 = bits.RotateLeft32(k1, 15) + k1 *= c2_32 + h1 ^= k1 + } + + h1 ^= uint32(d.clen) + + h1 ^= h1 >> 16 + h1 *= 0x85ebca6b + h1 ^= h1 >> 13 + h1 *= 0xc2b2ae35 + h1 ^= h1 >> 16 + + return h1 +} diff --git a/vendor/github.com/twmb/murmur3/murmur32_gen.go b/vendor/github.com/twmb/murmur3/murmur32_gen.go new file mode 100644 index 0000000..63c4cca --- /dev/null +++ b/vendor/github.com/twmb/murmur3/murmur32_gen.go @@ -0,0 +1,108 @@ +package murmur3 + +import "math/bits" + +// Sum32 returns the murmur3 sum of data. It is equivalent to the following +// sequence (without the extra burden and the extra allocation): +// +// hasher := New32() +// hasher.Write(data) +// return hasher.Sum32() +func Sum32(data []byte) uint32 { + return SeedSum32(0, data) +} + +// SeedSum32 returns the murmur3 sum of data with the digest initialized to +// seed. +// +// This reads and processes the data in chunks of little endian uint32s; +// thus, the returned hash is portable across architectures. +func SeedSum32(seed uint32, data []byte) (h1 uint32) { + h1 = seed + clen := uint32(len(data)) + for len(data) >= 4 { + k1 := uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16 | uint32(data[3])<<24 + data = data[4:] + + k1 *= c1_32 + k1 = bits.RotateLeft32(k1, 15) + k1 *= c2_32 + + h1 ^= k1 + h1 = bits.RotateLeft32(h1, 13) + h1 = h1*5 + 0xe6546b64 + } + var k1 uint32 + switch len(data) { + case 3: + k1 ^= uint32(data[2]) << 16 + fallthrough + case 2: + k1 ^= uint32(data[1]) << 8 + fallthrough + case 1: + k1 ^= uint32(data[0]) + k1 *= c1_32 + k1 = bits.RotateLeft32(k1, 15) + k1 *= c2_32 + h1 ^= k1 + } + + h1 ^= uint32(clen) + + h1 ^= h1 >> 16 + h1 *= 0x85ebca6b + h1 ^= h1 >> 13 + h1 *= 0xc2b2ae35 + h1 ^= h1 >> 16 + + return h1 +} + +// StringSum32 is the string version of Sum32. +func StringSum32(data string) uint32 { + return SeedStringSum32(0, data) +} + +// SeedStringSum32 is the string version of SeedSum32. +func SeedStringSum32(seed uint32, data string) (h1 uint32) { + h1 = seed + clen := uint32(len(data)) + for len(data) >= 4 { + k1 := uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16 | uint32(data[3])<<24 + data = data[4:] + + k1 *= c1_32 + k1 = bits.RotateLeft32(k1, 15) + k1 *= c2_32 + + h1 ^= k1 + h1 = bits.RotateLeft32(h1, 13) + h1 = h1*5 + 0xe6546b64 + } + var k1 uint32 + switch len(data) { + case 3: + k1 ^= uint32(data[2]) << 16 + fallthrough + case 2: + k1 ^= uint32(data[1]) << 8 + fallthrough + case 1: + k1 ^= uint32(data[0]) + k1 *= c1_32 + k1 = bits.RotateLeft32(k1, 15) + k1 *= c2_32 + h1 ^= k1 + } + + h1 ^= uint32(clen) + + h1 ^= h1 >> 16 + h1 *= 0x85ebca6b + h1 ^= h1 >> 13 + h1 *= 0xc2b2ae35 + h1 ^= h1 >> 16 + + return h1 +} diff --git a/vendor/github.com/twmb/murmur3/murmur64.go b/vendor/github.com/twmb/murmur3/murmur64.go new file mode 100644 index 0000000..3dfd1d9 --- /dev/null +++ b/vendor/github.com/twmb/murmur3/murmur64.go @@ -0,0 +1,70 @@ +package murmur3 + +import ( + "hash" +) + +// Make sure interfaces are correctly implemented. +var ( + _ hash.Hash = new(digest64) + _ hash.Hash64 = new(digest64) + _ bmixer = new(digest64) +) + +// digest64 is half a digest128. +type digest64 digest128 + +// SeedNew64 returns a hash.Hash64 for streaming 64 bit sums. As the canonical +// implementation does not support Sum64, this uses SeedNew128(seed, seed) +func SeedNew64(seed uint64) hash.Hash64 { + return (*digest64)(SeedNew128(seed, seed).(*digest128)) +} + +// New64 returns a hash.Hash64 for streaming 64 bit sums. +func New64() hash.Hash64 { + return SeedNew64(0) +} + +func (d *digest64) Sum(b []byte) []byte { + h1 := d.Sum64() + return append(b, + byte(h1>>56), byte(h1>>48), byte(h1>>40), byte(h1>>32), + byte(h1>>24), byte(h1>>16), byte(h1>>8), byte(h1)) +} + +func (d *digest64) Sum64() uint64 { + h1, _ := (*digest128)(d).Sum128() + return h1 +} + +// Sum64 returns the murmur3 sum of data. It is equivalent to the following +// sequence (without the extra burden and the extra allocation): +// hasher := New64() +// hasher.Write(data) +// return hasher.Sum64() +func Sum64(data []byte) uint64 { + h1, _ := Sum128(data) + return h1 +} + +// SeedSum64 returns the murmur3 sum of data with the digest initialized to +// seed. +// +// Because the canonical implementation does not support SeedSum64, this uses +// SeedSum128(seed, seed, data). +func SeedSum64(seed uint64, data []byte) uint64 { + h1, _ := SeedSum128(seed, seed, data) + return h1 +} + +// StringSum64 is the string version of Sum64. +func StringSum64(data string) uint64 { + h1, _ := StringSum128(data) + return h1 +} + +// SeedStringSum64 is the string version of SeedSum64. +func SeedStringSum64(seed uint64, data string) uint64 { + h1, _ := SeedStringSum128(seed, seed, data) + return h1 +} diff --git a/vendor/github.com/uber-go/tally/v4/.gitignore b/vendor/github.com/uber-go/tally/v4/.gitignore new file mode 100644 index 0000000..8b20825 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/.gitignore @@ -0,0 +1,34 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test +vendor +bin + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +*.pprof +*.out +*.log + +.DS_Store +node_modules/ +.idea/ +cover.html diff --git a/vendor/github.com/uber-go/tally/v4/.travis.yml b/vendor/github.com/uber-go/tally/v4/.travis.yml new file mode 100644 index 0000000..c43d421 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/.travis.yml @@ -0,0 +1,19 @@ +language: go +sudo: false +go: + - 1.18.x + - 1.19.x + - 1.20.x + - 1.21.x +cache: + directories: + - vendor +install: + - npm i uber-licence + - make dependencies +script: + - make test + - make lint +after_success: + - make coveralls + diff --git a/vendor/github.com/uber-go/tally/v4/LICENSE b/vendor/github.com/uber-go/tally/v4/LICENSE new file mode 100644 index 0000000..a8fc851 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/uber-go/tally/v4/Makefile b/vendor/github.com/uber-go/tally/v4/Makefile new file mode 100644 index 0000000..fb58346 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/Makefile @@ -0,0 +1,98 @@ +# "go install"-ed binaries will be placed here during development. +export GOBIN ?= $(shell pwd)/bin + +BENCH_FLAGS ?= -cpuprofile=cpu.pprof -memprofile=mem.pprof -benchmem +GO_FILES = $(shell find . \ + '(' -path '*/.*' -o -path './thirdparty/*' -prune ')' -o \ + '(' -type f -a -name '*.go' ')' -print) +MODULES = . ./tools + +LINT_IGNORE = m3/thrift\|thirdparty +LICENSE_IGNORE = m3/thrift\|thirdparty +STATICCHECK_IGNORE = m3/thrift\|thirdparty\|m3/resource_pool.go:.*releaseProto is unused\|m3/reporter.go:.* argument should be pointer-like to avoid allocations + +GOLINT = $(GOBIN)/golint +STATICCHECK = $(GOBIN)/staticcheck + +.PHONY: all +all: lint test + +.PHONY: lint +lint: gofmt golint gomodtidy staticcheck license + +.PHONY: golint +golint: $(GOLINT) + @echo "Checking lint..." + @$(eval LOG := $(shell mktemp -t log.XXXXX)) + @$(GOLINT) ./... | grep -v '$(LINT_IGNORE)' > $(LOG) || true + @[ ! -s "$(LOG)" ] || \ + (echo "golint failed:" | \ + cat - $(LOG) && false) + +$(GOLINT): tools/go.mod + cd tools && go install golang.org/x/lint/golint + +.PHONY: staticcheck +staticcheck: $(STATICCHECK) + @echo "Checking staticcheck..." + @$(eval LOG := $(shell mktemp -t log.XXXXX)) + @$(STATICCHECK) ./... | grep -v '$(STATICCHECK_IGNORE)' > $(LOG) || true + @[ ! -s "$(LOG)" ] || \ + (echo "staticcheck failed:" | \ + cat - $(LOG) && false) + +$(STATICCHECK): + cd tools && go install honnef.co/go/tools/cmd/staticcheck + +.PHONY: gofmt +gofmt: + @echo "Checking formatting..." + $(eval LOG := $(shell mktemp -t log.XXXXX)) + @gofmt -e -s -l $(GO_FILES) | grep -v '$(LINT_IGNORE)' > $(LOG) || true + @[ ! -s "$(LOG)" ] || \ + (echo "gofmt failed. Please reformat the following files:" | \ + cat - $(LOG) && false) + + +.PHONY: gomodtidy +gomodtidy: go.mod go.sum + @echo "Checking go.mod and go.sum..." + @$(foreach mod,$(MODULES),\ + (cd $(mod) && go mod tidy) &&) true + @if ! git diff --quiet $^; then \ + echo "go mod tidy changed files:" && \ + git status --porcelain $^ && \ + false; \ + fi + +.PHONY: license +license: check_license.sh + @echo "Checking for license headers..." + $(eval LOG := $(shell mktemp -t log.XXXXX)) + @./check_license.sh | grep -v '$(LICENSE_IGNORE)' > $(LOG) || true + @[ ! -s "$(LOG)" ] || \ + (echo "Missing license headers in some files:" | \ + cat - $(LOG) && false) + +.PHONY: test +test: + go test -race -v ./... + +.PHONY: examples +examples: + mkdir -p ./bin + go build -o ./bin/print_example ./example/ + go build -o ./bin/m3_example ./m3/example/ + go build -o ./bin/prometheus_example ./prometheus/example/ + go build -o ./bin/statsd_example ./statsd/example/ + +.PHONY: cover +cover: + go test -cover -coverprofile=cover.out -coverpkg=./... -race -v ./... + go tool cover -html=cover.out -o cover.html + +.PHONY: bench +BENCH ?= . +bench: + go test -bench=$(BENCH) -run="^$$" $(BENCH_FLAGS) ./... + diff --git a/vendor/github.com/uber-go/tally/v4/README.md b/vendor/github.com/uber-go/tally/v4/README.md new file mode 100644 index 0000000..54f12a5 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/README.md @@ -0,0 +1,212 @@ +# :heavy_check_mark: tally [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] + +Fast, buffered, hierarchical stats collection in Go. + +## Installation +`go get -u github.com/uber-go/tally` + +## Abstract + +Tally provides a common interface for emitting metrics, while letting you not worry about the velocity of metrics emission. + +By default it buffers counters, gauges and histograms at a specified interval but does not buffer timer values. This is primarily so timer values can have all their values sampled if desired and if not they can be sampled as summaries or histograms independently by a reporter. + +## Structure + +- Scope: Keeps track of metrics, and their common metadata. +- Metrics: Counters, Gauges, Timers and Histograms. +- Reporter: Implemented by you. Accepts aggregated values from the scope. Forwards the aggregated values to your metrics ingestion pipeline. + - The reporters already available listed alphabetically are: + - `github.com/uber-go/tally/m3`: Report m3 metrics, timers are not sampled and forwarded directly. + - `github.com/uber-go/tally/multi`: Report to multiple reporters, you can multi-write metrics to other reporters simply. + - `github.com/uber-go/tally/prometheus`: Report prometheus metrics, timers by default are made summaries with an option to make them histograms instead. + - `github.com/uber-go/tally/statsd`: Report statsd metrics, no support for tags. + +### Basics + + - Scopes created with tally provide race-safe registration and use of all metric types `Counter`, `Gauge`, `Timer`, `Histogram`. + - `NewRootScope(...)` returns a `Scope` and `io.Closer`, the second return value is used to stop the scope's goroutine reporting values from the scope to it's reporter. This is to reduce the footprint of `Scope` from the public API for those implementing it themselves to use in Go packages that take a tally `Scope`. + +### Acquire a Scope ### +```go +reporter = NewMyStatsReporter() // Implement as you will +tags := map[string]string{ + "dc": "east-1", + "type": "master", +} +reportEvery := time.Second + +scope := tally.NewRootScope(tally.ScopeOptions{ + Tags: tags, + Reporter: reporter, +}, reportEvery) +``` + +### Get/Create a metric, use it ### +```go +// Get a counter, increment a counter +reqCounter := scope.Counter("requests") // cache me +reqCounter.Inc(1) + +queueGauge := scope.Gauge("queue_length") // cache me +queueGauge.Update(42) +``` + +### Report your metrics ### +Use the inbuilt statsd reporter: + +```go +import ( + "io" + "github.com/cactus/go-statsd-client/v5/statsd" + "github.com/uber-go/tally" + tallystatsd "github.com/uber-go/tally/statsd" + // ... +) + +func newScope() (tally.Scope, io.Closer) { + statter, _ := statsd.NewBufferedClient("127.0.0.1:8125", + "stats", 100*time.Millisecond, 1440) + + reporter := tallystatsd.NewReporter(statter, tallystatsd.Options{ + SampleRate: 1.0, + }) + + scope, closer := tally.NewRootScope(tally.ScopeOptions{ + Prefix: "my-service", + Tags: map[string]string{}, + Reporter: reporter, + }, time.Second) + + return scope, closer +} +``` + +Implement your own reporter using the `StatsReporter` interface: + +```go + +// BaseStatsReporter implements the shared reporter methods. +type BaseStatsReporter interface { + Capabilities() Capabilities + Flush() +} + +// StatsReporter is a backend for Scopes to report metrics to. +type StatsReporter interface { + BaseStatsReporter + + // ReportCounter reports a counter value + ReportCounter( + name string, + tags map[string]string, + value int64, + ) + + // ReportGauge reports a gauge value + ReportGauge( + name string, + tags map[string]string, + value float64, + ) + + // ReportTimer reports a timer value + ReportTimer( + name string, + tags map[string]string, + interval time.Duration, + ) + + // ReportHistogramValueSamples reports histogram samples for a bucket + ReportHistogramValueSamples( + name string, + tags map[string]string, + buckets Buckets, + bucketLowerBound, + bucketUpperBound float64, + samples int64, + ) + + // ReportHistogramDurationSamples reports histogram samples for a bucket + ReportHistogramDurationSamples( + name string, + tags map[string]string, + buckets Buckets, + bucketLowerBound, + bucketUpperBound time.Duration, + samples int64, + ) +} +``` + +Or implement your own metrics implementation that matches the tally `Scope` interface to use different buffering semantics: + +```go +type Scope interface { + // Counter returns the Counter object corresponding to the name. + Counter(name string) Counter + + // Gauge returns the Gauge object corresponding to the name. + Gauge(name string) Gauge + + // Timer returns the Timer object corresponding to the name. + Timer(name string) Timer + + // Histogram returns the Histogram object corresponding to the name. + // To use default value and duration buckets configured for the scope + // simply pass tally.DefaultBuckets or nil. + // You can use tally.ValueBuckets{x, y, ...} for value buckets. + // You can use tally.DurationBuckets{x, y, ...} for duration buckets. + // You can use tally.MustMakeLinearValueBuckets(start, width, count) for linear values. + // You can use tally.MustMakeLinearDurationBuckets(start, width, count) for linear durations. + // You can use tally.MustMakeExponentialValueBuckets(start, factor, count) for exponential values. + // You can use tally.MustMakeExponentialDurationBuckets(start, factor, count) for exponential durations. + Histogram(name string, buckets Buckets) Histogram + + // Tagged returns a new child scope with the given tags and current tags. + Tagged(tags map[string]string) Scope + + // SubScope returns a new child scope appending a further name prefix. + SubScope(name string) Scope + + // Capabilities returns a description of metrics reporting capabilities. + Capabilities() Capabilities +} + +// Capabilities is a description of metrics reporting capabilities. +type Capabilities interface { + // Reporting returns whether the reporter has the ability to actively report. + Reporting() bool + + // Tagging returns whether the reporter has the capability for tagged metrics. + Tagging() bool +} +``` + +## Performance + +This stuff needs to be fast. With that in mind, we avoid locks and unnecessary memory allocations. + +``` +BenchmarkCounterInc-8 200000000 7.68 ns/op +BenchmarkReportCounterNoData-8 300000000 4.88 ns/op +BenchmarkReportCounterWithData-8 100000000 21.6 ns/op +BenchmarkGaugeSet-8 100000000 16.0 ns/op +BenchmarkReportGaugeNoData-8 100000000 10.4 ns/op +BenchmarkReportGaugeWithData-8 50000000 27.6 ns/op +BenchmarkTimerInterval-8 50000000 37.7 ns/op +BenchmarkTimerReport-8 300000000 5.69 ns/op +``` + +
+ +Released under the [MIT License](LICENSE). + +[doc-img]: https://godoc.org/github.com/uber-go/tally?status.svg +[doc]: https://godoc.org/github.com/uber-go/tally +[ci-img]: https://travis-ci.org/uber-go/tally.svg?branch=master +[ci]: https://travis-ci.org/uber-go/tally +[cov-img]: https://coveralls.io/repos/github/uber-go/tally/badge.svg?branch=master +[cov]: https://coveralls.io/github/uber-go/tally?branch=master +[glide.lock]: https://github.com/uber-go/tally/blob/master/glide.lock +[v1]: https://github.com/uber-go/tally/milestones diff --git a/vendor/github.com/uber-go/tally/v4/check_license.sh b/vendor/github.com/uber-go/tally/v4/check_license.sh new file mode 100644 index 0000000..2366ec8 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/check_license.sh @@ -0,0 +1,16 @@ +#!/bin/bash -e + +ERROR_COUNT=0 +while read -r file +do + case "$(head -1 "${file}")" in + *"Copyright (c) "*" Uber Technologies, Inc.") + # everything's cool + ;; + *) + echo "$file:missing license header." + (( ERROR_COUNT++ )) + ;; + esac +done < <(git ls-files "*\.go") +exit "$ERROR_COUNT" diff --git a/vendor/github.com/uber-go/tally/v4/generate.go b/vendor/github.com/uber-go/tally/v4/generate.go new file mode 100644 index 0000000..30192d7 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/generate.go @@ -0,0 +1,26 @@ +// Copyright (c) 2024 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tally + +import ( + //go:generate mockgen -package tallymock -destination tallymock/stats_reporter.go -imports github.com/uber-go/tally github.com/uber-go/tally StatsReporter + _ "github.com/golang/mock/mockgen/model" +) diff --git a/vendor/github.com/uber-go/tally/v4/histogram.go b/vendor/github.com/uber-go/tally/v4/histogram.go new file mode 100644 index 0000000..41491d9 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/histogram.go @@ -0,0 +1,384 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tally + +import ( + "errors" + "fmt" + "math" + "sort" + "time" +) + +var ( + // DefaultBuckets can be passed to specify to default buckets. + DefaultBuckets Buckets + + errBucketsCountNeedsGreaterThanZero = errors.New("n needs to be > 0") + errBucketsStartNeedsGreaterThanZero = errors.New("start needs to be > 0") + errBucketsFactorNeedsGreaterThanOne = errors.New("factor needs to be > 1") + + _singleBucket = bucketPair{ + lowerBoundDuration: time.Duration(math.MinInt64), + upperBoundDuration: time.Duration(math.MaxInt64), + lowerBoundValue: -math.MaxFloat64, + upperBoundValue: math.MaxFloat64, + } +) + +// ValueBuckets is a set of float64 values that implements Buckets. +type ValueBuckets []float64 + +// Implements sort.Interface +func (v ValueBuckets) Len() int { + return len(v) +} + +// Implements sort.Interface +func (v ValueBuckets) Swap(i, j int) { + v[i], v[j] = v[j], v[i] +} + +// Implements sort.Interface +func (v ValueBuckets) Less(i, j int) bool { + return v[i] < v[j] +} + +func (v ValueBuckets) String() string { + values := make([]string, len(v)) + for i := range values { + values[i] = fmt.Sprintf("%f", v[i]) + } + return fmt.Sprint(values) +} + +// AsValues implements Buckets. +func (v ValueBuckets) AsValues() []float64 { + return v +} + +// AsDurations implements Buckets and returns time.Duration +// representations of the float64 values divided by time.Second. +func (v ValueBuckets) AsDurations() []time.Duration { + values := make([]time.Duration, len(v)) + for i := range values { + values[i] = time.Duration(v[i] * float64(time.Second)) + } + return values +} + +// DurationBuckets is a set of time.Duration values that implements Buckets. +type DurationBuckets []time.Duration + +// Implements sort.Interface +func (v DurationBuckets) Len() int { + return len(v) +} + +// Implements sort.Interface +func (v DurationBuckets) Swap(i, j int) { + v[i], v[j] = v[j], v[i] +} + +// Implements sort.Interface +func (v DurationBuckets) Less(i, j int) bool { + return v[i] < v[j] +} + +func (v DurationBuckets) String() string { + values := make([]string, len(v)) + for i := range values { + values[i] = v[i].String() + } + return fmt.Sprintf("%v", values) +} + +// AsValues implements Buckets and returns float64 +// representations of the time.Duration values divided by time.Second. +func (v DurationBuckets) AsValues() []float64 { + values := make([]float64, len(v)) + for i := range values { + values[i] = float64(v[i]) / float64(time.Second) + } + return values +} + +// AsDurations implements Buckets. +func (v DurationBuckets) AsDurations() []time.Duration { + return v +} + +func bucketsEqual(x Buckets, y Buckets) bool { + switch b1 := x.(type) { + case DurationBuckets: + b2, ok := y.(DurationBuckets) + if !ok { + return false + } + if len(b1) != len(b2) { + return false + } + for i := 0; i < len(b1); i++ { + if b1[i] != b2[i] { + return false + } + } + case ValueBuckets: + b2, ok := y.(ValueBuckets) + if !ok { + return false + } + if len(b1) != len(b2) { + return false + } + for i := 0; i < len(b1); i++ { + if b1[i] != b2[i] { + return false + } + } + } + + return true +} + +func newBucketPair( + htype histogramType, + durations []time.Duration, + values []float64, + upperBoundIndex int, + prev BucketPair, +) bucketPair { + var pair bucketPair + + switch htype { + case durationHistogramType: + pair = bucketPair{ + lowerBoundDuration: prev.UpperBoundDuration(), + upperBoundDuration: durations[upperBoundIndex], + } + case valueHistogramType: + pair = bucketPair{ + lowerBoundValue: prev.UpperBoundValue(), + upperBoundValue: values[upperBoundIndex], + } + default: + // nop + } + + return pair +} + +// BucketPairs creates a set of bucket pairs from a set +// of buckets describing the lower and upper bounds for +// each derived bucket. +func BucketPairs(buckets Buckets) []BucketPair { + htype := valueHistogramType + if _, ok := buckets.(DurationBuckets); ok { + htype = durationHistogramType + } + + if buckets == nil || buckets.Len() < 1 { + return []BucketPair{_singleBucket} + } + + var ( + values []float64 + durations []time.Duration + pairs = make([]BucketPair, 0, buckets.Len()+2) + pair bucketPair + ) + + switch htype { + case durationHistogramType: + durations = copyAndSortDurations(buckets.AsDurations()) + pair.lowerBoundDuration = _singleBucket.lowerBoundDuration + pair.upperBoundDuration = durations[0] + case valueHistogramType: + values = copyAndSortValues(buckets.AsValues()) + pair.lowerBoundValue = _singleBucket.lowerBoundValue + pair.upperBoundValue = values[0] + default: + // n.b. This branch will never be executed because htype is only ever + // one of two values. + panic("unsupported histogram type") + } + + pairs = append(pairs, pair) + for i := 1; i < buckets.Len(); i++ { + pairs = append( + pairs, + newBucketPair(htype, durations, values, i, pairs[i-1]), + ) + } + + switch htype { + case durationHistogramType: + pair.lowerBoundDuration = pairs[len(pairs)-1].UpperBoundDuration() + pair.upperBoundDuration = _singleBucket.upperBoundDuration + case valueHistogramType: + pair.lowerBoundValue = pairs[len(pairs)-1].UpperBoundValue() + pair.upperBoundValue = _singleBucket.upperBoundValue + } + pairs = append(pairs, pair) + + return pairs +} + +func copyAndSortValues(values []float64) []float64 { + valuesCopy := make([]float64, len(values)) + copy(valuesCopy, values) + sort.Sort(ValueBuckets(valuesCopy)) + return valuesCopy +} + +func copyAndSortDurations(durations []time.Duration) []time.Duration { + durationsCopy := make([]time.Duration, len(durations)) + copy(durationsCopy, durations) + sort.Sort(DurationBuckets(durationsCopy)) + return durationsCopy +} + +type bucketPair struct { + lowerBoundValue float64 + upperBoundValue float64 + lowerBoundDuration time.Duration + upperBoundDuration time.Duration +} + +func (p bucketPair) LowerBoundValue() float64 { + return p.lowerBoundValue +} + +func (p bucketPair) UpperBoundValue() float64 { + return p.upperBoundValue +} + +func (p bucketPair) LowerBoundDuration() time.Duration { + return p.lowerBoundDuration +} + +func (p bucketPair) UpperBoundDuration() time.Duration { + return p.upperBoundDuration +} + +// LinearValueBuckets creates a set of linear value buckets. +func LinearValueBuckets(start, width float64, n int) (ValueBuckets, error) { + if n <= 0 { + return nil, errBucketsCountNeedsGreaterThanZero + } + buckets := make([]float64, n) + for i := range buckets { + buckets[i] = start + (float64(i) * width) + } + return buckets, nil +} + +// MustMakeLinearValueBuckets creates a set of linear value buckets +// or panics. +func MustMakeLinearValueBuckets(start, width float64, n int) ValueBuckets { + buckets, err := LinearValueBuckets(start, width, n) + if err != nil { + panic(err) + } + return buckets +} + +// LinearDurationBuckets creates a set of linear duration buckets. +func LinearDurationBuckets(start, width time.Duration, n int) (DurationBuckets, error) { + if n <= 0 { + return nil, errBucketsCountNeedsGreaterThanZero + } + buckets := make([]time.Duration, n) + for i := range buckets { + buckets[i] = start + (time.Duration(i) * width) + } + return buckets, nil +} + +// MustMakeLinearDurationBuckets creates a set of linear duration buckets. +// or panics. +func MustMakeLinearDurationBuckets(start, width time.Duration, n int) DurationBuckets { + buckets, err := LinearDurationBuckets(start, width, n) + if err != nil { + panic(err) + } + return buckets +} + +// ExponentialValueBuckets creates a set of exponential value buckets. +func ExponentialValueBuckets(start, factor float64, n int) (ValueBuckets, error) { + if n <= 0 { + return nil, errBucketsCountNeedsGreaterThanZero + } + if start <= 0 { + return nil, errBucketsStartNeedsGreaterThanZero + } + if factor <= 1 { + return nil, errBucketsFactorNeedsGreaterThanOne + } + buckets := make([]float64, n) + curr := start + for i := range buckets { + buckets[i] = curr + curr *= factor + } + return buckets, nil +} + +// MustMakeExponentialValueBuckets creates a set of exponential value buckets +// or panics. +func MustMakeExponentialValueBuckets(start, factor float64, n int) ValueBuckets { + buckets, err := ExponentialValueBuckets(start, factor, n) + if err != nil { + panic(err) + } + return buckets +} + +// ExponentialDurationBuckets creates a set of exponential duration buckets. +func ExponentialDurationBuckets(start time.Duration, factor float64, n int) (DurationBuckets, error) { + if n <= 0 { + return nil, errBucketsCountNeedsGreaterThanZero + } + if start <= 0 { + return nil, errBucketsStartNeedsGreaterThanZero + } + if factor <= 1 { + return nil, errBucketsFactorNeedsGreaterThanOne + } + buckets := make([]time.Duration, n) + curr := start + for i := range buckets { + buckets[i] = curr + curr = time.Duration(float64(curr) * factor) + } + return buckets, nil +} + +// MustMakeExponentialDurationBuckets creates a set of exponential value buckets +// or panics. +func MustMakeExponentialDurationBuckets(start time.Duration, factor float64, n int) DurationBuckets { + buckets, err := ExponentialDurationBuckets(start, factor, n) + if err != nil { + panic(err) + } + return buckets +} diff --git a/vendor/github.com/uber-go/tally/v4/internal/identity/accumulator.go b/vendor/github.com/uber-go/tally/v4/internal/identity/accumulator.go new file mode 100644 index 0000000..83987c5 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/internal/identity/accumulator.go @@ -0,0 +1,131 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package identity + +import ( + "math" + "time" + + "github.com/twmb/murmur3" +) + +const ( + _hashSeed uint64 = 23 + _hashFold uint64 = 31 +) + +// Accumulator is a commutative folding accumulator. +type Accumulator uint64 + +// NewAccumulator creates a new Accumulator with a default seed value. +// +// n.b. Here and elsewhere, we use nosplit to avoid stack size checks, which +// are unnecessary as memory width is bounded to each instance of `a` (a +// uint64) and, potentially, a single stack-local loop temporary while +// iterating. +func NewAccumulator() Accumulator { + return Accumulator(_hashSeed) +} + +// NewAccumulatorWithSeed creates a new Accumulator with the provided seed value. +func NewAccumulatorWithSeed(seed uint64) Accumulator { + return Accumulator(seed) +} + +// AddString hashes str and folds it into the accumulator. +func (a Accumulator) AddString(str string) Accumulator { + return a + Accumulator(murmur3.StringSum64(str)*_hashFold) +} + +// AddUint64 folds u64 into the accumulator. +func (a Accumulator) AddUint64(u64 uint64) Accumulator { + return a + Accumulator(u64*_hashFold) +} + +// Value returns the accumulated value. +func (a Accumulator) Value() uint64 { + return uint64(a) +} + +// Durations returns the accumulated identity of durs. +func Durations(durs []time.Duration) uint64 { + if len(durs) == 0 { + return 0 + } + + acc := NewAccumulator() + + // n.b. Wrapping due to overflow is okay here, since those values cannot be + // represented by int64. + for _, d := range durs { + acc = acc.AddUint64(uint64(d)) + } + + return acc.Value() +} + +// Int64s returns the accumulated identity of i64s. +func Int64s(i64s []int64) uint64 { + if len(i64s) == 0 { + return 0 + } + + acc := NewAccumulator() + + // n.b. Wrapping due to overflow is okay here, since those values cannot be + // represented by int64. + for _, i := range i64s { + acc = acc.AddUint64(uint64(i)) + } + + return acc.Value() +} + +// Float64s returns the accumulated identity of f64s. +func Float64s(f64s []float64) uint64 { + if len(f64s) == 0 { + return 0 + } + + // n.b. Wrapping due to overflow is okay here, since those values cannot be + // represented by int64. + acc := NewAccumulator() + + for _, f := range f64s { + acc = acc.AddUint64(math.Float64bits(f)) + } + + return acc.Value() +} + +// StringStringMap returns the accumulated identity of m. +func StringStringMap(m map[string]string) uint64 { + if len(m) == 0 { + return 0 + } + + acc := NewAccumulator() + for k, v := range m { + acc = acc.AddString(k + "=" + v) + } + + return acc.Value() +} diff --git a/vendor/github.com/uber-go/tally/v4/key_gen.go b/vendor/github.com/uber-go/tally/v4/key_gen.go new file mode 100644 index 0000000..bd2cde7 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/key_gen.go @@ -0,0 +1,107 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tally + +const ( + prefixSplitter = '+' + keyPairSplitter = ',' + keyNameSplitter = '=' + nilString = "" +) + +// KeyForStringMap generates a unique key for a map string set combination. +func KeyForStringMap( + stringMap map[string]string, +) string { + return KeyForPrefixedStringMap(nilString, stringMap) +} + +// KeyForPrefixedStringMap generates a unique key for a +// a prefix and a map string set combination. +func KeyForPrefixedStringMap( + prefix string, + stringMap map[string]string, +) string { + return keyForPrefixedStringMaps(prefix, stringMap) +} + +// keyForPrefixedStringMapsAsKey writes a key using the prefix and the tags in a canonical form to +// the given input byte slice and returns a reference to the byte slice. Callers of this method can +// use a stack allocated byte slice to remove heap allocation. +func keyForPrefixedStringMapsAsKey(buf []byte, prefix string, maps ...map[string]string) []byte { + // stack allocated + keys := make([]string, 0, 32) + for _, m := range maps { + for k := range m { + keys = append(keys, k) + } + } + + insertionSort(keys) + + if prefix != nilString { + buf = append(buf, prefix...) + buf = append(buf, prefixSplitter) + } + + var lastKey string // last key written to the buffer + for _, k := range keys { + if len(lastKey) > 0 { + if k == lastKey { + // Already wrote this key. + continue + } + buf = append(buf, keyPairSplitter) + } + lastKey = k + + buf = append(buf, k...) + buf = append(buf, keyNameSplitter) + + // Find and write the value for this key. Rightmost map takes + // precedence. + for j := len(maps) - 1; j >= 0; j-- { + if v, ok := maps[j][k]; ok { + buf = append(buf, v...) + break + } + } + } + + return buf +} + +// keyForPrefixedStringMaps generates a unique key for a prefix and a series +// of maps containing tags. +// +// If a key occurs in multiple maps, keys on the right take precedence. +func keyForPrefixedStringMaps(prefix string, maps ...map[string]string) string { + return string(keyForPrefixedStringMapsAsKey(make([]byte, 0, 256), prefix, maps...)) +} + +func insertionSort(keys []string) { + n := len(keys) + for i := 1; i < n; i++ { + for j := i; j > 0 && keys[j] < keys[j-1]; j-- { + keys[j], keys[j-1] = keys[j-1], keys[j] + } + } +} diff --git a/vendor/github.com/uber-go/tally/v4/pool.go b/vendor/github.com/uber-go/tally/v4/pool.go new file mode 100644 index 0000000..4d8b140 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/pool.go @@ -0,0 +1,63 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tally + +// ObjectPool is an minimalistic object pool to avoid +// any circular dependencies on any other object pool. +type ObjectPool struct { + values chan interface{} + alloc func() interface{} +} + +// NewObjectPool creates a new pool. +func NewObjectPool(size int) *ObjectPool { + return &ObjectPool{ + values: make(chan interface{}, size), + } +} + +// Init initializes the object pool. +func (p *ObjectPool) Init(alloc func() interface{}) { + p.alloc = alloc + + for i := 0; i < cap(p.values); i++ { + p.values <- p.alloc() + } +} + +// Get gets an object from the pool. +func (p *ObjectPool) Get() interface{} { + var v interface{} + select { + case v = <-p.values: + default: + v = p.alloc() + } + return v +} + +// Put puts an object back to the pool. +func (p *ObjectPool) Put(obj interface{}) { + select { + case p.values <- obj: + default: + } +} diff --git a/vendor/github.com/uber-go/tally/v4/prometheus/README.md b/vendor/github.com/uber-go/tally/v4/prometheus/README.md new file mode 100644 index 0000000..3f6fdad --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/prometheus/README.md @@ -0,0 +1,118 @@ +# A buffered Prometheus reporter + +See `examples/prometheus_main.go` for an end to end example. + +## Options + +You can use a specific Prometheus registry, and you can use +either summaries or histograms for timers. + +The reporter options are: + +```go +// Options is a set of options for the tally reporter. +type Options struct { + // Registerer is the prometheus registerer to register + // metrics with. Use nil to specify the default registerer. + Registerer prom.Registerer + + // DefaultTimerType is the default type timer type to create + // when using timers. It's default value is a histogram timer type. + DefaultTimerType TimerType + + // DefaultHistogramBuckets is the default histogram buckets + // to use. Use nil to specify the default histogram buckets. + DefaultHistogramBuckets []float64 + + // DefaultSummaryObjectives is the default summary objectives + // to use. Use nil to specify the default summary objectives. + DefaultSummaryObjectives map[float64]float64 + + // OnRegisterError defines a method to call to when registering + // a metric with the registerer fails. Use nil to specify + // to panic by default when registering a metric fails. + OnRegisterError func(err error) +} +``` + +The timer types are: + +```go +// TimerType describes a type of timer +type TimerType int + +const ( + // SummaryTimerType is a timer type that reports into a summary + SummaryTimerType TimerType = iota + + // HistogramTimerType is a timer type that reports into a histogram + HistogramTimerType +) +``` + +You can also pre-register help description text ahead of using a metric +that will be named and tagged identically with `tally`. You can also +access the Prometheus HTTP handler directly. + +The returned reporter interface: + +```go +// Reporter is a Prometheus backed tally reporter. +type Reporter interface { + tally.CachedStatsReporter + + // HTTPHandler provides the Prometheus HTTP scrape handler. + HTTPHandler() http.Handler + + // RegisterCounter is a helper method to initialize a counter + // in the Prometheus backend with a given help text. + // If not called explicitly, the Reporter will create one for + // you on first use, with a not super helpful HELP string. + RegisterCounter( + name string, + tagKeys []string, + desc string, + ) (*prom.CounterVec, error) + + // RegisterGauge is a helper method to initialize a gauge + // in the prometheus backend with a given help text. + // If not called explicitly, the Reporter will create one for + // you on first use, with a not super helpful HELP string. + RegisterGauge( + name string, + tagKeys []string, + desc string, + ) (*prom.GaugeVec, error) + + // RegisterTimer is a helper method to initialize a timer + // summary or histogram vector in the prometheus backend + // with a given help text. + // If not called explicitly, the Reporter will create one for + // you on first use, with a not super helpful HELP string. + // You may pass opts as nil to get the default timer type + // and objectives/buckets. + // You may also pass objectives/buckets as nil in opts to + // get the default objectives/buckets for the specified + // timer type. + RegisterTimer( + name string, + tagKeys []string, + desc string, + opts *RegisterTimerOptions, + ) (TimerUnion, error) +} +``` + +The register timer options: + +```go +// RegisterTimerOptions provides options when registering a timer on demand. +// By default you can pass nil for the options to get the reporter defaults. +type RegisterTimerOptions struct { + TimerType TimerType + HistogramBuckets []float64 + SummaryObjectives map[float64]float64 +} +``` + + diff --git a/vendor/github.com/uber-go/tally/v4/prometheus/config.go b/vendor/github.com/uber-go/tally/v4/prometheus/config.go new file mode 100644 index 0000000..9885253 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/prometheus/config.go @@ -0,0 +1,176 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package prometheus + +import ( + "fmt" + "log" + "net" + "net/http" + "os" + "strings" + + prom "github.com/prometheus/client_golang/prometheus" +) + +// Configuration is a configuration for a Prometheus reporter. +type Configuration struct { + // HandlerPath if specified will be used instead of using the default + // HTTP handler path "/metrics". + HandlerPath string `yaml:"handlerPath"` + + // ListenNetwork if specified will be used instead of using tcp network. + // Supported networks: tcp, tcp4, tcp6 and unix. + ListenNetwork string `yaml:"listenNetwork"` + + // ListenAddress if specified will be used instead of just registering the + // handler on the default HTTP serve mux without listening. + ListenAddress string `yaml:"listenAddress"` + + // TimerType is the default Prometheus type to use for Tally timers. + TimerType string `yaml:"timerType"` + + // DefaultHistogramBuckets if specified will set the default histogram + // buckets to be used by the reporter. + DefaultHistogramBuckets []HistogramObjective `yaml:"defaultHistogramBuckets"` + + // DefaultSummaryObjectives if specified will set the default summary + // objectives to be used by the reporter. + DefaultSummaryObjectives []SummaryObjective `yaml:"defaultSummaryObjectives"` + + // OnError specifies what to do when an error either with listening + // on the specified listen address or registering a metric with the + // Prometheus. By default the registerer will panic. + OnError string `yaml:"onError"` +} + +// HistogramObjective is a Prometheus histogram bucket. +// See: https://godoc.org/github.com/prometheus/client_golang/prometheus#HistogramOpts +type HistogramObjective struct { + Upper float64 `yaml:"upper"` +} + +// SummaryObjective is a Prometheus summary objective. +// See: https://godoc.org/github.com/prometheus/client_golang/prometheus#SummaryOpts +type SummaryObjective struct { + Percentile float64 `yaml:"percentile"` + AllowedError float64 `yaml:"allowedError"` +} + +// ConfigurationOptions allows some programatic options, such as using a +// specific registry and what error callback to register. +type ConfigurationOptions struct { + // Registry if not nil will specify the specific registry to use + // for registering metrics. + Registry *prom.Registry + // OnError allows for customization of what to do when a metric + // registration error fails, the default is to panic. + OnError func(e error) +} + +// NewReporter creates a new M3 reporter from this configuration. +func (c Configuration) NewReporter( + configOpts ConfigurationOptions, +) (Reporter, error) { + var opts Options + + if configOpts.Registry != nil { + opts.Registerer = configOpts.Registry + } + + if configOpts.OnError != nil { + opts.OnRegisterError = configOpts.OnError + } else { + switch c.OnError { + case "stderr": + opts.OnRegisterError = func(err error) { + fmt.Fprintf(os.Stderr, "tally prometheus reporter error: %v\n", err) + } + case "log": + opts.OnRegisterError = func(err error) { + log.Printf("tally prometheus reporter error: %v\n", err) + } + case "none": + opts.OnRegisterError = func(err error) {} + default: + opts.OnRegisterError = func(err error) { + panic(err) + } + } + } + + switch c.TimerType { + case "summary": + opts.DefaultTimerType = SummaryTimerType + case "histogram": + opts.DefaultTimerType = HistogramTimerType + } + + if len(c.DefaultHistogramBuckets) > 0 { + var values []float64 + for _, value := range c.DefaultHistogramBuckets { + values = append(values, value.Upper) + } + opts.DefaultHistogramBuckets = values + } + + if len(c.DefaultSummaryObjectives) > 0 { + values := make(map[float64]float64) + for _, value := range c.DefaultSummaryObjectives { + values[value.Percentile] = value.AllowedError + } + opts.DefaultSummaryObjectives = values + } + + reporter := NewReporter(opts) + + path := "/metrics" + if handlerPath := strings.TrimSpace(c.HandlerPath); handlerPath != "" { + path = handlerPath + } + + if addr := strings.TrimSpace(c.ListenAddress); addr == "" { + http.Handle(path, reporter.HTTPHandler()) + } else { + mux := http.NewServeMux() + mux.Handle(path, reporter.HTTPHandler()) + go func() { + network := c.ListenNetwork + if network == "" { + network = "tcp" + } + + listener, err := net.Listen(network, addr) + if err != nil { + opts.OnRegisterError(err) + return + } + + defer listener.Close() + + if err = http.Serve(listener, mux); err != nil { + opts.OnRegisterError(err) + } + }() + } + + return reporter, nil +} diff --git a/vendor/github.com/uber-go/tally/v4/prometheus/reporter.go b/vendor/github.com/uber-go/tally/v4/prometheus/reporter.go new file mode 100644 index 0000000..4807f19 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/prometheus/reporter.go @@ -0,0 +1,610 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package prometheus + +import ( + "net/http" + "strings" + "sync" + "time" + + "github.com/pkg/errors" + prom "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + tally "github.com/uber-go/tally/v4" +) + +const ( + // DefaultSeparator is the default separator that should be used with + // a tally scope for a prometheus reporter. + DefaultSeparator = "_" +) + +var ( + errUnknownTimerType = errors.New("unknown metric timer type") + ms = float64(time.Millisecond) / float64(time.Second) +) + +// DefaultHistogramBuckets is the default histogram buckets used when +// creating a new Histogram in the prometheus registry. +// See: https://godoc.org/github.com/prometheus/client_golang/prometheus#HistogramOpts +func DefaultHistogramBuckets() []float64 { + return []float64{ + ms, + 2 * ms, + 5 * ms, + 10 * ms, + 20 * ms, + 50 * ms, + 100 * ms, + 200 * ms, + 500 * ms, + 1000 * ms, + 2000 * ms, + 5000 * ms, + 10000 * ms, + } +} + +// DefaultSummaryObjectives is the default objectives used when +// creating a new Summary in the prometheus registry. +// See: https://godoc.org/github.com/prometheus/client_golang/prometheus#SummaryOpts +func DefaultSummaryObjectives() map[float64]float64 { + return map[float64]float64{ + 0.5: 0.01, + 0.75: 0.001, + 0.95: 0.001, + 0.99: 0.001, + 0.999: 0.0001, + } +} + +// Reporter is a Prometheus backed tally reporter. +type Reporter interface { + tally.CachedStatsReporter + + // HTTPHandler provides the Prometheus HTTP scrape handler. + HTTPHandler() http.Handler + + // RegisterCounter is a helper method to initialize a counter + // in the Prometheus backend with a given help text. + // If not called explicitly, the Reporter will create one for + // you on first use, with a not super helpful HELP string. + RegisterCounter( + name string, + tagKeys []string, + desc string, + ) (*prom.CounterVec, error) + + // RegisterGauge is a helper method to initialize a gauge + // in the prometheus backend with a given help text. + // If not called explicitly, the Reporter will create one for + // you on first use, with a not super helpful HELP string. + RegisterGauge( + name string, + tagKeys []string, + desc string, + ) (*prom.GaugeVec, error) + + // RegisterTimer is a helper method to initialize a timer + // summary or histogram vector in the prometheus backend + // with a given help text. + // If not called explicitly, the Reporter will create one for + // you on first use, with a not super helpful HELP string. + // You may pass opts as nil to get the default timer type + // and objectives/buckets. + // You may also pass objectives/buckets as nil in opts to + // get the default objectives/buckets for the specified + // timer type. + RegisterTimer( + name string, + tagKeys []string, + desc string, + opts *RegisterTimerOptions, + ) (TimerUnion, error) +} + +// RegisterTimerOptions provides options when registering a timer on demand. +// By default you can pass nil for the options to get the reporter defaults. +type RegisterTimerOptions struct { + TimerType TimerType + HistogramBuckets []float64 + SummaryObjectives map[float64]float64 +} + +// TimerUnion is a representation of either a summary or a histogram +// described by the TimerType. +type TimerUnion struct { + TimerType TimerType + Histogram *prom.HistogramVec + Summary *prom.SummaryVec +} + +type metricID string + +type reporter struct { + sync.RWMutex + registerer prom.Registerer + gatherer prom.Gatherer + timerType TimerType + objectives map[float64]float64 + buckets []float64 + onRegisterError func(e error) + counters map[metricID]*prom.CounterVec + gauges map[metricID]*prom.GaugeVec + timers map[metricID]*promTimerVec +} + +type promTimerVec struct { + summary *prom.SummaryVec + histogram *prom.HistogramVec +} + +type cachedMetric struct { + counter prom.Counter + gauge prom.Gauge + reportTimer func(d time.Duration) + histogram prom.Observer + summary prom.Observer +} + +func (m *cachedMetric) ReportCount(value int64) { + m.counter.Add(float64(value)) +} + +func (m *cachedMetric) ReportGauge(value float64) { + m.gauge.Set(value) +} + +func (m *cachedMetric) ReportTimer(interval time.Duration) { + m.reportTimer(interval) +} + +func (m *cachedMetric) reportTimerHistogram(interval time.Duration) { + m.histogram.Observe(float64(interval) / float64(time.Second)) +} + +func (m *cachedMetric) reportTimerSummary(interval time.Duration) { + m.summary.Observe(float64(interval) / float64(time.Second)) +} + +func (m *cachedMetric) ValueBucket( + bucketLowerBound, bucketUpperBound float64, +) tally.CachedHistogramBucket { + return cachedHistogramBucket{m, bucketUpperBound} +} + +func (m *cachedMetric) DurationBucket( + bucketLowerBound, bucketUpperBound time.Duration, +) tally.CachedHistogramBucket { + upperBound := float64(bucketUpperBound) / float64(time.Second) + return cachedHistogramBucket{m, upperBound} +} + +type cachedHistogramBucket struct { + metric *cachedMetric + upperBound float64 +} + +func (b cachedHistogramBucket) ReportSamples(value int64) { + for i := int64(0); i < value; i++ { + b.metric.histogram.Observe(b.upperBound) + } +} + +type noopMetric struct{} + +func (m noopMetric) ReportCount(value int64) {} +func (m noopMetric) ReportGauge(value float64) {} +func (m noopMetric) ReportTimer(interval time.Duration) {} +func (m noopMetric) ReportSamples(value int64) {} +func (m noopMetric) ValueBucket(lower, upper float64) tally.CachedHistogramBucket { + return m +} + +func (m noopMetric) DurationBucket(lower, upper time.Duration) tally.CachedHistogramBucket { + return m +} + +func (r *reporter) HTTPHandler() http.Handler { + return promhttp.HandlerFor(r.gatherer, promhttp.HandlerOpts{}) +} + +// TimerType describes a type of timer +type TimerType int + +const ( + // SummaryTimerType is a timer type that reports into a summary + SummaryTimerType TimerType = iota + + // HistogramTimerType is a timer type that reports into a histogram + HistogramTimerType +) + +// Options is a set of options for the tally reporter. +type Options struct { + // Registerer is the prometheus registerer to register + // metrics with. Use nil to specify the default registerer. + Registerer prom.Registerer + + // Gatherer is the prometheus gatherer to gather + // metrics with. Use nil to specify the default gatherer. + Gatherer prom.Gatherer + + // DefaultTimerType is the default type timer type to create + // when using timers. It's default value is a summary timer type. + DefaultTimerType TimerType + + // DefaultHistogramBuckets is the default histogram buckets + // to use. Use nil to specify the default histogram buckets. + DefaultHistogramBuckets []float64 + + // DefaultSummaryObjectives is the default summary objectives + // to use. Use nil to specify the default summary objectives. + DefaultSummaryObjectives map[float64]float64 + + // OnRegisterError defines a method to call to when registering + // a metric with the registerer fails. Use nil to specify + // to panic by default when registering fails. + OnRegisterError func(err error) +} + +// NewReporter returns a new Reporter for Prometheus client backed metrics +// objectives is the objectives used when creating a new Summary histogram for Timers. See +// https://godoc.org/github.com/prometheus/client_golang/prometheus#SummaryOpts for more details. +func NewReporter(opts Options) Reporter { + if opts.Registerer == nil { + opts.Registerer = prom.DefaultRegisterer + } else { + // A specific registerer was set, check if it's a registry and if + // no gatherer was set, then use that as the gatherer + if reg, ok := opts.Registerer.(*prom.Registry); ok && opts.Gatherer == nil { + opts.Gatherer = reg + } + } + if opts.Gatherer == nil { + opts.Gatherer = prom.DefaultGatherer + } + if opts.DefaultHistogramBuckets == nil { + opts.DefaultHistogramBuckets = DefaultHistogramBuckets() + } + if opts.DefaultSummaryObjectives == nil { + opts.DefaultSummaryObjectives = DefaultSummaryObjectives() + } + if opts.OnRegisterError == nil { + opts.OnRegisterError = func(err error) { + // n.b. Because our forked Prometheus client does not actually emit + // this message as a concrete error type (it uses fmt.Errorf), + // we need to check the error message. + if strings.Contains(err.Error(), "previously registered") { + err = errors.WithMessagef( + err, + "potential tally.Scope() vs Prometheus usage contract mismatch: "+ + "if this occurs after using Scope.Tagged(), different metric "+ + "names must be used than were registered with the parent scope", + ) + } + + panic(err) + } + } + + return &reporter{ + registerer: opts.Registerer, + gatherer: opts.Gatherer, + timerType: opts.DefaultTimerType, + buckets: opts.DefaultHistogramBuckets, + objectives: opts.DefaultSummaryObjectives, + onRegisterError: opts.OnRegisterError, + counters: make(map[metricID]*prom.CounterVec), + gauges: make(map[metricID]*prom.GaugeVec), + timers: make(map[metricID]*promTimerVec), + } +} + +func (r *reporter) RegisterCounter( + name string, + tagKeys []string, + desc string, +) (*prom.CounterVec, error) { + return r.counterVec(name, tagKeys, desc) +} + +func (r *reporter) counterVec( + name string, + tagKeys []string, + desc string, +) (*prom.CounterVec, error) { + id := canonicalMetricID(name, tagKeys) + + r.Lock() + defer r.Unlock() + + if ctr, ok := r.counters[id]; ok { + return ctr, nil + } + + ctr := prom.NewCounterVec( + prom.CounterOpts{ + Name: name, + Help: desc, + }, + tagKeys, + ) + + if err := r.registerer.Register(ctr); err != nil { + return nil, err + } + + r.counters[id] = ctr + return ctr, nil +} + +// AllocateCounter implements tally.CachedStatsReporter. +func (r *reporter) AllocateCounter(name string, tags map[string]string) tally.CachedCount { + tagKeys := keysFromMap(tags) + counterVec, err := r.counterVec(name, tagKeys, name+" counter") + if err != nil { + r.onRegisterError(err) + return noopMetric{} + } + return &cachedMetric{counter: counterVec.With(tags)} +} + +func (r *reporter) RegisterGauge( + name string, + tagKeys []string, + desc string, +) (*prom.GaugeVec, error) { + return r.gaugeVec(name, tagKeys, desc) +} + +func (r *reporter) gaugeVec( + name string, + tagKeys []string, + desc string, +) (*prom.GaugeVec, error) { + id := canonicalMetricID(name, tagKeys) + + r.Lock() + defer r.Unlock() + + if g, ok := r.gauges[id]; ok { + return g, nil + } + + g := prom.NewGaugeVec( + prom.GaugeOpts{ + Name: name, + Help: desc, + }, + tagKeys, + ) + + if err := r.registerer.Register(g); err != nil { + return nil, err + } + + r.gauges[id] = g + return g, nil +} + +// AllocateGauge implements tally.CachedStatsReporter. +func (r *reporter) AllocateGauge(name string, tags map[string]string) tally.CachedGauge { + tagKeys := keysFromMap(tags) + gaugeVec, err := r.gaugeVec(name, tagKeys, name+" gauge") + if err != nil { + r.onRegisterError(err) + return noopMetric{} + } + return &cachedMetric{gauge: gaugeVec.With(tags)} +} + +func (r *reporter) RegisterTimer( + name string, + tagKeys []string, + desc string, + opts *RegisterTimerOptions, +) (TimerUnion, error) { + timerType, buckets, objectives := r.timerConfig(opts) + switch timerType { + case HistogramTimerType: + h, err := r.histogramVec(name, tagKeys, desc, buckets) + return TimerUnion{TimerType: timerType, Histogram: h}, err + case SummaryTimerType: + s, err := r.summaryVec(name, tagKeys, desc, objectives) + return TimerUnion{TimerType: timerType, Summary: s}, err + } + return TimerUnion{}, errUnknownTimerType +} + +func (r *reporter) timerConfig( + opts *RegisterTimerOptions, +) ( + timerType TimerType, + buckets []float64, + objectives map[float64]float64, +) { + timerType = r.timerType + objectives = r.objectives + buckets = r.buckets + if opts != nil { + timerType = opts.TimerType + if opts.SummaryObjectives != nil { + objectives = opts.SummaryObjectives + } + if opts.HistogramBuckets != nil { + buckets = opts.HistogramBuckets + } + } + return +} + +func (r *reporter) summaryVec( + name string, + tagKeys []string, + desc string, + objectives map[float64]float64, +) (*prom.SummaryVec, error) { + id := canonicalMetricID(name, tagKeys) + + r.Lock() + defer r.Unlock() + + if s, ok := r.timers[id]; ok { + return s.summary, nil + } + + s := prom.NewSummaryVec( + prom.SummaryOpts{ + Name: name, + Help: desc, + Objectives: objectives, + }, + tagKeys, + ) + + if err := r.registerer.Register(s); err != nil { + return nil, err + } + + r.timers[id] = &promTimerVec{summary: s} + return s, nil +} + +func (r *reporter) histogramVec( + name string, + tagKeys []string, + desc string, + buckets []float64, +) (*prom.HistogramVec, error) { + id := canonicalMetricID(name, tagKeys) + + r.Lock() + defer r.Unlock() + + if h, ok := r.timers[id]; ok { + return h.histogram, nil + } + + h := prom.NewHistogramVec( + prom.HistogramOpts{ + Name: name, + Help: desc, + Buckets: buckets, + }, + tagKeys, + ) + + if err := r.registerer.Register(h); err != nil { + return nil, err + } + + r.timers[id] = &promTimerVec{histogram: h} + return h, nil +} + +// AllocateTimer implements tally.CachedStatsReporter. +func (r *reporter) AllocateTimer(name string, tags map[string]string) tally.CachedTimer { + var ( + timer tally.CachedTimer + err error + ) + tagKeys := keysFromMap(tags) + timerType, buckets, objectives := r.timerConfig(nil) + switch timerType { + case HistogramTimerType: + var histogramVec *prom.HistogramVec + histogramVec, err = r.histogramVec(name, tagKeys, name+" histogram", buckets) + if err == nil { + t := &cachedMetric{histogram: histogramVec.With(tags)} + t.reportTimer = t.reportTimerHistogram + timer = t + } + case SummaryTimerType: + var summaryVec *prom.SummaryVec + summaryVec, err = r.summaryVec(name, tagKeys, name+" summary", objectives) + if err == nil { + t := &cachedMetric{summary: summaryVec.With(tags)} + t.reportTimer = t.reportTimerSummary + timer = t + } + default: + err = errUnknownTimerType + } + if err != nil { + r.onRegisterError(err) + return noopMetric{} + } + return timer +} + +func (r *reporter) AllocateHistogram( + name string, + tags map[string]string, + buckets tally.Buckets, +) tally.CachedHistogram { + tagKeys := keysFromMap(tags) + histogramVec, err := r.histogramVec(name, tagKeys, name+" histogram", buckets.AsValues()) + if err != nil { + r.onRegisterError(err) + return noopMetric{} + } + return &cachedMetric{histogram: histogramVec.With(tags)} +} + +func (r *reporter) Capabilities() tally.Capabilities { + return r +} + +func (r *reporter) Reporting() bool { + return true +} + +func (r *reporter) Tagging() bool { + return true +} + +// Flush does nothing for prometheus +func (r *reporter) Flush() {} + +var metricIDKeyValue = "1" + +// NOTE: this generates a canonical MetricID for a given name+label keys, +// not values. This omits label values, as we track metrics as +// Vectors in order to support on-the-fly label changes. +func canonicalMetricID(name string, tagKeys []string) metricID { + keySet := make(map[string]string, len(tagKeys)) + for _, key := range tagKeys { + keySet[key] = metricIDKeyValue + } + return metricID(tally.KeyForPrefixedStringMap(name, keySet)) +} + +func keysFromMap(m map[string]string) []string { + labelKeys := make([]string, len(m)) + i := 0 + for k := range m { + labelKeys[i] = k + i++ + } + return labelKeys +} diff --git a/vendor/github.com/uber-go/tally/v4/prometheus/sanitize.go b/vendor/github.com/uber-go/tally/v4/prometheus/sanitize.go new file mode 100644 index 0000000..6c3f7f1 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/prometheus/sanitize.go @@ -0,0 +1,42 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package prometheus + +import ( + tally "github.com/uber-go/tally/v4" +) + +// DefaultSanitizerOpts are the options for the default Prometheus sanitizer. +var DefaultSanitizerOpts = tally.SanitizeOptions{ + NameCharacters: tally.ValidCharacters{ + Ranges: tally.AlphanumericRange, + Characters: tally.UnderscoreCharacters, + }, + KeyCharacters: tally.ValidCharacters{ + Ranges: tally.AlphanumericRange, + Characters: tally.UnderscoreCharacters, + }, + ValueCharacters: tally.ValidCharacters{ + Ranges: tally.AlphanumericRange, + Characters: tally.UnderscoreCharacters, + }, + ReplacementCharacter: tally.DefaultReplacementCharacter, +} diff --git a/vendor/github.com/uber-go/tally/v4/reporter.go b/vendor/github.com/uber-go/tally/v4/reporter.go new file mode 100644 index 0000000..22e0f26 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/reporter.go @@ -0,0 +1,140 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tally + +import "time" + +// BaseStatsReporter implements the shared reporter methods. +type BaseStatsReporter interface { + // Capabilities returns the capabilities description of the reporter. + Capabilities() Capabilities + + // Flush asks the reporter to flush all reported values. + Flush() +} + +// StatsReporter is a backend for Scopes to report metrics to. +type StatsReporter interface { + BaseStatsReporter + + // ReportCounter reports a counter value + ReportCounter( + name string, + tags map[string]string, + value int64, + ) + + // ReportGauge reports a gauge value + ReportGauge( + name string, + tags map[string]string, + value float64, + ) + + // ReportTimer reports a timer value + ReportTimer( + name string, + tags map[string]string, + interval time.Duration, + ) + + // ReportHistogramValueSamples reports histogram samples for a bucket + ReportHistogramValueSamples( + name string, + tags map[string]string, + buckets Buckets, + bucketLowerBound, + bucketUpperBound float64, + samples int64, + ) + + // ReportHistogramDurationSamples reports histogram samples for a bucket + ReportHistogramDurationSamples( + name string, + tags map[string]string, + buckets Buckets, + bucketLowerBound, + bucketUpperBound time.Duration, + samples int64, + ) +} + +// CachedStatsReporter is a backend for Scopes that pre allocates all +// counter, gauges, timers & histograms. This is harder to implement but more performant. +type CachedStatsReporter interface { + BaseStatsReporter + + // AllocateCounter pre allocates a counter data structure with name & tags. + AllocateCounter( + name string, + tags map[string]string, + ) CachedCount + + // AllocateGauge pre allocates a gauge data structure with name & tags. + AllocateGauge( + name string, + tags map[string]string, + ) CachedGauge + + // AllocateTimer pre allocates a timer data structure with name & tags. + AllocateTimer( + name string, + tags map[string]string, + ) CachedTimer + + // AllocateHistogram pre allocates a histogram data structure with name, tags, + // value buckets and duration buckets. + AllocateHistogram( + name string, + tags map[string]string, + buckets Buckets, + ) CachedHistogram +} + +// CachedCount interface for reporting an individual counter +type CachedCount interface { + ReportCount(value int64) +} + +// CachedGauge interface for reporting an individual gauge +type CachedGauge interface { + ReportGauge(value float64) +} + +// CachedTimer interface for reporting an individual timer +type CachedTimer interface { + ReportTimer(interval time.Duration) +} + +// CachedHistogram interface for reporting histogram samples to buckets +type CachedHistogram interface { + ValueBucket( + bucketLowerBound, bucketUpperBound float64, + ) CachedHistogramBucket + DurationBucket( + bucketLowerBound, bucketUpperBound time.Duration, + ) CachedHistogramBucket +} + +// CachedHistogramBucket interface for reporting histogram samples to a specific bucket +type CachedHistogramBucket interface { + ReportSamples(value int64) +} diff --git a/vendor/github.com/uber-go/tally/v4/sanitize.go b/vendor/github.com/uber-go/tally/v4/sanitize.go new file mode 100644 index 0000000..2a0f7ed --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/sanitize.go @@ -0,0 +1,194 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tally + +import ( + "bytes" + "sync" +) + +var ( + // DefaultReplacementCharacter is the default character used for + // replacements. + DefaultReplacementCharacter = '_' + + // AlphanumericRange is the range of alphanumeric characters. + AlphanumericRange = []SanitizeRange{ + {rune('a'), rune('z')}, + {rune('A'), rune('Z')}, + {rune('0'), rune('9')}} + + // UnderscoreCharacters is just an underscore character. + UnderscoreCharacters = []rune{ + '_'} + + // UnderscoreDashCharacters is a slice of underscore, and + // dash characters. + UnderscoreDashCharacters = []rune{ + '-', + '_'} + + // UnderscoreDashDotCharacters is a slice of underscore, + // dash, and dot characters. + UnderscoreDashDotCharacters = []rune{ + '.', + '-', + '_'} +) + +// SanitizeFn returns a sanitized version of the input string. +type SanitizeFn func(string) string + +// SanitizeRange is a range of characters (inclusive on both ends). +type SanitizeRange [2]rune + +// ValidCharacters is a collection of valid characters. +type ValidCharacters struct { + Ranges []SanitizeRange + Characters []rune +} + +// SanitizeOptions are the set of configurable options for sanitisation. +type SanitizeOptions struct { + NameCharacters ValidCharacters + KeyCharacters ValidCharacters + ValueCharacters ValidCharacters + ReplacementCharacter rune +} + +// Sanitizer sanitizes the provided input based on the function executed. +type Sanitizer interface { + // Name sanitizes the provided `name` string. + Name(n string) string + + // Key sanitizes the provided `key` string. + Key(k string) string + + // Value sanitizes the provided `value` string. + Value(v string) string +} + +// NewSanitizer returns a new sanitizer based on provided options. +func NewSanitizer(opts SanitizeOptions) Sanitizer { + return sanitizer{ + nameFn: opts.NameCharacters.sanitizeFn(opts.ReplacementCharacter), + keyFn: opts.KeyCharacters.sanitizeFn(opts.ReplacementCharacter), + valueFn: opts.ValueCharacters.sanitizeFn(opts.ReplacementCharacter), + } +} + +// NoOpSanitizeFn returns the input un-touched. +func NoOpSanitizeFn(v string) string { return v } + +// NewNoOpSanitizer returns a sanitizer which returns all inputs un-touched. +func NewNoOpSanitizer() Sanitizer { + return sanitizer{ + nameFn: NoOpSanitizeFn, + keyFn: NoOpSanitizeFn, + valueFn: NoOpSanitizeFn, + } +} + +type sanitizer struct { + nameFn SanitizeFn + keyFn SanitizeFn + valueFn SanitizeFn +} + +func (s sanitizer) Name(n string) string { + return s.nameFn(n) +} + +func (s sanitizer) Key(k string) string { + return s.keyFn(k) +} + +func (s sanitizer) Value(v string) string { + return s.valueFn(v) +} + +var _sanitizeBuffers = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +func getSanitizeBuffer() *bytes.Buffer { + return _sanitizeBuffers.Get().(*bytes.Buffer) +} + +func putSanitizeBuffer(b *bytes.Buffer) { + b.Reset() + _sanitizeBuffers.Put(b) +} + +func (c *ValidCharacters) sanitizeFn(repChar rune) SanitizeFn { + return func(value string) string { + var buf *bytes.Buffer + for idx, ch := range value { + // first check if the provided character is valid + validCurr := false + for i := 0; !validCurr && i < len(c.Ranges); i++ { + if ch >= c.Ranges[i][0] && ch <= c.Ranges[i][1] { + validCurr = true + break + } + } + for i := 0; !validCurr && i < len(c.Characters); i++ { + if c.Characters[i] == ch { + validCurr = true + break + } + } + + // if it's valid, we can optimise allocations by avoiding copying + if validCurr { + if buf == nil { + continue // haven't deviated from string, still no need to init buffer + } + buf.WriteRune(ch) // we've deviated from string, write to buffer + continue + } + + // ie the character is invalid, and the buffer has not been initialised + // so we initialise buffer and backfill + if buf == nil { + buf = getSanitizeBuffer() + if idx > 0 { + buf.WriteString(value[:idx]) + } + } + + // write the replacement character + buf.WriteRune(repChar) + } + + // return input un-touched if the buffer has been not initialised + if buf == nil { + return value + } + + // otherwise, return the newly constructed buffer + result := buf.String() + putSanitizeBuffer(buf) + return result + } +} diff --git a/vendor/github.com/uber-go/tally/v4/scope.go b/vendor/github.com/uber-go/tally/v4/scope.go new file mode 100644 index 0000000..733cd4e --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/scope.go @@ -0,0 +1,800 @@ +// Copyright (c) 2024 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tally + +import ( + "io" + "sync" + "time" + + "go.uber.org/atomic" +) + +const ( + _defaultInitialSliceSize = 16 + _defaultReportingInterval = 2 * time.Second +) + +var ( + // NoopScope is a scope that does nothing + NoopScope, _ = NewRootScope(ScopeOptions{Reporter: NullStatsReporter}, 0) + // DefaultSeparator is the default separator used to join nested scopes + DefaultSeparator = "." + + globalNow = time.Now + + defaultScopeBuckets = DurationBuckets{ + 0 * time.Millisecond, + 10 * time.Millisecond, + 25 * time.Millisecond, + 50 * time.Millisecond, + 75 * time.Millisecond, + 100 * time.Millisecond, + 200 * time.Millisecond, + 300 * time.Millisecond, + 400 * time.Millisecond, + 500 * time.Millisecond, + 600 * time.Millisecond, + 800 * time.Millisecond, + 1 * time.Second, + 2 * time.Second, + 5 * time.Second, + } +) + +type scope struct { + separator string + prefix string + tags map[string]string + reporter StatsReporter + cachedReporter CachedStatsReporter + baseReporter BaseStatsReporter + defaultBuckets Buckets + sanitizer Sanitizer + + registry *scopeRegistry + + cm sync.RWMutex + gm sync.RWMutex + tm sync.RWMutex + hm sync.RWMutex + + counters map[string]*counter + countersSlice []*counter + gauges map[string]*gauge + gaugesSlice []*gauge + histograms map[string]*histogram + histogramsSlice []*histogram + timers map[string]*timer + // nb: deliberately skipping timersSlice as we report timers immediately, + // no buffering is involved. + + bucketCache *bucketCache + closed atomic.Bool + done chan struct{} + wg sync.WaitGroup + root bool + testScope bool +} + +// ScopeOptions is a set of options to construct a scope. +type ScopeOptions struct { + Tags map[string]string + Prefix string + Reporter StatsReporter + CachedReporter CachedStatsReporter + Separator string + DefaultBuckets Buckets + SanitizeOptions *SanitizeOptions + OmitCardinalityMetrics bool + CardinalityMetricsTags map[string]string + + testScope bool + registryShardCount uint +} + +// NewRootScope creates a new root Scope with a set of options and +// a reporting interval. +// Must provide either a StatsReporter or a CachedStatsReporter. +func NewRootScope(opts ScopeOptions, interval time.Duration) (Scope, io.Closer) { + s := newRootScope(opts, interval) + return s, s +} + +// NewRootScopeWithDefaultInterval invokes NewRootScope with the default +// reporting interval of 2s. +func NewRootScopeWithDefaultInterval(opts ScopeOptions) (Scope, io.Closer) { + return NewRootScope(opts, _defaultReportingInterval) +} + +// NewTestScope creates a new Scope without a stats reporter with the +// given prefix and adds the ability to take snapshots of metrics emitted +// to it. +func NewTestScope( + prefix string, + tags map[string]string, +) TestScope { + return newRootScope(ScopeOptions{ + Prefix: prefix, + Tags: tags, + testScope: true, + }, 0) +} + +func newRootScope(opts ScopeOptions, interval time.Duration) *scope { + sanitizer := NewNoOpSanitizer() + if o := opts.SanitizeOptions; o != nil { + sanitizer = NewSanitizer(*o) + } + + if opts.Tags == nil { + opts.Tags = make(map[string]string) + } + if opts.Separator == "" { + opts.Separator = DefaultSeparator + } + + var baseReporter BaseStatsReporter + if opts.Reporter != nil { + baseReporter = opts.Reporter + } else if opts.CachedReporter != nil { + baseReporter = opts.CachedReporter + } + + if opts.DefaultBuckets == nil || opts.DefaultBuckets.Len() < 1 { + opts.DefaultBuckets = defaultScopeBuckets + } + + s := &scope{ + baseReporter: baseReporter, + bucketCache: newBucketCache(), + cachedReporter: opts.CachedReporter, + counters: make(map[string]*counter), + countersSlice: make([]*counter, 0, _defaultInitialSliceSize), + defaultBuckets: opts.DefaultBuckets, + done: make(chan struct{}), + gauges: make(map[string]*gauge), + gaugesSlice: make([]*gauge, 0, _defaultInitialSliceSize), + histograms: make(map[string]*histogram), + histogramsSlice: make([]*histogram, 0, _defaultInitialSliceSize), + prefix: sanitizer.Name(opts.Prefix), + reporter: opts.Reporter, + sanitizer: sanitizer, + separator: sanitizer.Name(opts.Separator), + timers: make(map[string]*timer), + root: true, + testScope: opts.testScope, + } + + // NB(r): Take a copy of the tags on creation + // so that it cannot be modified after set. + s.tags = s.copyAndSanitizeMap(opts.Tags) + + // Register the root scope + s.registry = newScopeRegistryWithShardCount(s, opts.registryShardCount, opts.OmitCardinalityMetrics, opts.CardinalityMetricsTags) + + if interval > 0 { + s.wg.Add(1) + go func() { + defer s.wg.Done() + s.reportLoop(interval) + }() + } + + return s +} + +// report dumps all aggregated stats into the reporter. Should be called automatically by the root scope periodically. +func (s *scope) report(r StatsReporter) { + s.cm.RLock() + for name, counter := range s.counters { + counter.report(s.fullyQualifiedName(name), s.tags, r) + } + s.cm.RUnlock() + + s.gm.RLock() + for name, gauge := range s.gauges { + gauge.report(s.fullyQualifiedName(name), s.tags, r) + } + s.gm.RUnlock() + + // we do nothing for timers here because timers report directly to ths StatsReporter without buffering + + s.hm.RLock() + for name, histogram := range s.histograms { + histogram.report(s.fullyQualifiedName(name), s.tags, r) + } + s.hm.RUnlock() +} + +func (s *scope) cachedReport() { + s.cm.RLock() + for _, counter := range s.countersSlice { + counter.cachedReport() + } + s.cm.RUnlock() + + s.gm.RLock() + for _, gauge := range s.gaugesSlice { + gauge.cachedReport() + } + s.gm.RUnlock() + + // we do nothing for timers here because timers report directly to ths StatsReporter without buffering + + s.hm.RLock() + for _, histogram := range s.histogramsSlice { + histogram.cachedReport() + } + s.hm.RUnlock() +} + +// reportLoop is used by the root scope for periodic reporting +func (s *scope) reportLoop(interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + s.reportLoopRun() + case <-s.done: + return + } + } +} + +func (s *scope) reportLoopRun() { + if s.closed.Load() { + return + } + + s.reportRegistry() +} + +func (s *scope) reportRegistry() { + if s.reporter != nil { + s.registry.Report(s.reporter) + s.reporter.Flush() + } else if s.cachedReporter != nil { + s.registry.CachedReport() + s.cachedReporter.Flush() + } +} + +func (s *scope) Counter(name string) Counter { + name = s.sanitizer.Name(name) + if c, ok := s.counter(name); ok { + return c + } + + s.cm.Lock() + defer s.cm.Unlock() + + if c, ok := s.counters[name]; ok { + return c + } + + var cachedCounter CachedCount + if s.cachedReporter != nil { + cachedCounter = s.cachedReporter.AllocateCounter( + s.fullyQualifiedName(name), + s.tags, + ) + } + + c := newCounter(cachedCounter) + s.counters[name] = c + s.countersSlice = append(s.countersSlice, c) + + return c +} + +func (s *scope) counter(sanitizedName string) (Counter, bool) { + s.cm.RLock() + defer s.cm.RUnlock() + + c, ok := s.counters[sanitizedName] + return c, ok +} + +func (s *scope) Gauge(name string) Gauge { + name = s.sanitizer.Name(name) + if g, ok := s.gauge(name); ok { + return g + } + + s.gm.Lock() + defer s.gm.Unlock() + + if g, ok := s.gauges[name]; ok { + return g + } + + var cachedGauge CachedGauge + if s.cachedReporter != nil { + cachedGauge = s.cachedReporter.AllocateGauge( + s.fullyQualifiedName(name), s.tags, + ) + } + + g := newGauge(cachedGauge) + s.gauges[name] = g + s.gaugesSlice = append(s.gaugesSlice, g) + + return g +} + +func (s *scope) gauge(name string) (Gauge, bool) { + s.gm.RLock() + defer s.gm.RUnlock() + + g, ok := s.gauges[name] + return g, ok +} + +func (s *scope) Timer(name string) Timer { + name = s.sanitizer.Name(name) + if t, ok := s.timer(name); ok { + return t + } + + s.tm.Lock() + defer s.tm.Unlock() + + if t, ok := s.timers[name]; ok { + return t + } + + var cachedTimer CachedTimer + if s.cachedReporter != nil { + cachedTimer = s.cachedReporter.AllocateTimer( + s.fullyQualifiedName(name), s.tags, + ) + } + + t := newTimer( + s.fullyQualifiedName(name), s.tags, s.reporter, cachedTimer, + ) + s.timers[name] = t + + return t +} + +func (s *scope) timer(sanitizedName string) (Timer, bool) { + s.tm.RLock() + defer s.tm.RUnlock() + + t, ok := s.timers[sanitizedName] + return t, ok +} + +func (s *scope) Histogram(name string, b Buckets) Histogram { + name = s.sanitizer.Name(name) + if h, ok := s.histogram(name); ok { + return h + } + + if b == nil { + b = s.defaultBuckets + } + + htype := valueHistogramType + if _, ok := b.(DurationBuckets); ok { + htype = durationHistogramType + } + + s.hm.Lock() + defer s.hm.Unlock() + + if h, ok := s.histograms[name]; ok { + return h + } + + var cachedHistogram CachedHistogram + if s.cachedReporter != nil { + cachedHistogram = s.cachedReporter.AllocateHistogram( + s.fullyQualifiedName(name), s.tags, b, + ) + } + + h := newHistogram( + htype, + s.fullyQualifiedName(name), + s.tags, + s.reporter, + s.bucketCache.Get(htype, b), + cachedHistogram, + ) + s.histograms[name] = h + s.histogramsSlice = append(s.histogramsSlice, h) + + return h +} + +func (s *scope) histogram(sanitizedName string) (Histogram, bool) { + s.hm.RLock() + defer s.hm.RUnlock() + + h, ok := s.histograms[sanitizedName] + return h, ok +} + +func (s *scope) Tagged(tags map[string]string) Scope { + return s.subscope(s.prefix, tags) +} + +func (s *scope) SubScope(prefix string) Scope { + prefix = s.sanitizer.Name(prefix) + return s.subscope(s.fullyQualifiedName(prefix), nil) +} + +func (s *scope) subscope(prefix string, tags map[string]string) Scope { + return s.registry.Subscope(s, prefix, tags) +} + +func (s *scope) Capabilities() Capabilities { + if s.baseReporter == nil { + return capabilitiesNone + } + return s.baseReporter.Capabilities() +} + +func (s *scope) Snapshot() Snapshot { + snap := newSnapshot() + + s.registry.ForEachScope(func(ss *scope) { + // NB(r): tags are immutable, no lock required to read. + tags := make(map[string]string, len(s.tags)) + for k, v := range ss.tags { + tags[k] = v + } + + ss.cm.RLock() + for key, c := range ss.counters { + name := ss.fullyQualifiedName(key) + id := KeyForPrefixedStringMap(name, tags) + snap.counters[id] = &counterSnapshot{ + name: name, + tags: tags, + value: c.snapshot(), + } + } + ss.cm.RUnlock() + ss.gm.RLock() + for key, g := range ss.gauges { + name := ss.fullyQualifiedName(key) + id := KeyForPrefixedStringMap(name, tags) + snap.gauges[id] = &gaugeSnapshot{ + name: name, + tags: tags, + value: g.snapshot(), + } + } + ss.gm.RUnlock() + ss.tm.RLock() + for key, t := range ss.timers { + name := ss.fullyQualifiedName(key) + id := KeyForPrefixedStringMap(name, tags) + snap.timers[id] = &timerSnapshot{ + name: name, + tags: tags, + values: t.snapshot(), + } + } + ss.tm.RUnlock() + ss.hm.RLock() + for key, h := range ss.histograms { + name := ss.fullyQualifiedName(key) + id := KeyForPrefixedStringMap(name, tags) + snap.histograms[id] = &histogramSnapshot{ + name: name, + tags: tags, + values: h.snapshotValues(), + durations: h.snapshotDurations(), + } + } + ss.hm.RUnlock() + }) + + return snap +} + +func (s *scope) Close() error { + // n.b. Once this flag is set, the next scope report will remove it from + // the registry and clear its metrics. + if !s.closed.CAS(false, true) { + return nil + } + + close(s.done) + + if s.root { + s.reportRegistry() + if closer, ok := s.baseReporter.(io.Closer); ok { + return closer.Close() + } + } + + return nil +} + +func (s *scope) clearMetrics() { + s.cm.Lock() + s.gm.Lock() + s.tm.Lock() + s.hm.Lock() + defer s.cm.Unlock() + defer s.gm.Unlock() + defer s.tm.Unlock() + defer s.hm.Unlock() + + for k := range s.counters { + delete(s.counters, k) + } + s.countersSlice = nil + + for k := range s.gauges { + delete(s.gauges, k) + } + s.gaugesSlice = nil + + for k := range s.timers { + delete(s.timers, k) + } + + for k := range s.histograms { + delete(s.histograms, k) + } + s.histogramsSlice = nil +} + +// NB(prateek): We assume concatenation of sanitized inputs is +// sanitized. If that stops being true, then we need to sanitize the +// output of this function. +func (s *scope) fullyQualifiedName(name string) string { + if len(s.prefix) == 0 { + return name + } + // NB: we don't need to sanitize the output of this function as we + // sanitize all the the inputs (prefix, separator, name); and the + // output we're creating is a concatenation of the sanitized inputs. + // If we change the concatenation to involve other inputs or characters, + // we'll need to sanitize them too. + return s.prefix + s.separator + name +} + +func (s *scope) copyAndSanitizeMap(tags map[string]string) map[string]string { + result := make(map[string]string, len(tags)) + for k, v := range tags { + k = s.sanitizer.Key(k) + v = s.sanitizer.Value(v) + result[k] = v + } + return result +} + +// TestScope is a metrics collector that has no reporting, ensuring that +// all emitted values have a given prefix or set of tags +type TestScope interface { + Scope + + // Snapshot returns a copy of all values since the last report execution, + // this is an expensive operation and should only be use for testing purposes + Snapshot() Snapshot +} + +// Snapshot is a snapshot of values since last report execution +type Snapshot interface { + // Counters returns a snapshot of all counter summations since last report execution + Counters() map[string]CounterSnapshot + + // Gauges returns a snapshot of gauge last values since last report execution + Gauges() map[string]GaugeSnapshot + + // Timers returns a snapshot of timer values since last report execution + Timers() map[string]TimerSnapshot + + // Histograms returns a snapshot of histogram samples since last report execution + Histograms() map[string]HistogramSnapshot +} + +// CounterSnapshot is a snapshot of a counter +type CounterSnapshot interface { + // Name returns the name + Name() string + + // Tags returns the tags + Tags() map[string]string + + // Value returns the value + Value() int64 +} + +// GaugeSnapshot is a snapshot of a gauge +type GaugeSnapshot interface { + // Name returns the name + Name() string + + // Tags returns the tags + Tags() map[string]string + + // Value returns the value + Value() float64 +} + +// TimerSnapshot is a snapshot of a timer +type TimerSnapshot interface { + // Name returns the name + Name() string + + // Tags returns the tags + Tags() map[string]string + + // Values returns the values + Values() []time.Duration +} + +// HistogramSnapshot is a snapshot of a histogram +type HistogramSnapshot interface { + // Name returns the name + Name() string + + // Tags returns the tags + Tags() map[string]string + + // Values returns the sample values by upper bound for a valueHistogram + Values() map[float64]int64 + + // Durations returns the sample values by upper bound for a durationHistogram + Durations() map[time.Duration]int64 +} + +// mergeRightTags merges 2 sets of tags with the tags from tagsRight overriding values from tagsLeft +func mergeRightTags(tagsLeft, tagsRight map[string]string) map[string]string { + if tagsLeft == nil && tagsRight == nil { + return nil + } + if len(tagsRight) == 0 { + return tagsLeft + } + if len(tagsLeft) == 0 { + return tagsRight + } + + result := make(map[string]string, len(tagsLeft)+len(tagsRight)) + for k, v := range tagsLeft { + result[k] = v + } + for k, v := range tagsRight { + result[k] = v + } + return result +} + +type snapshot struct { + counters map[string]CounterSnapshot + gauges map[string]GaugeSnapshot + timers map[string]TimerSnapshot + histograms map[string]HistogramSnapshot +} + +func newSnapshot() *snapshot { + return &snapshot{ + counters: make(map[string]CounterSnapshot), + gauges: make(map[string]GaugeSnapshot), + timers: make(map[string]TimerSnapshot), + histograms: make(map[string]HistogramSnapshot), + } +} + +func (s *snapshot) Counters() map[string]CounterSnapshot { + return s.counters +} + +func (s *snapshot) Gauges() map[string]GaugeSnapshot { + return s.gauges +} + +func (s *snapshot) Timers() map[string]TimerSnapshot { + return s.timers +} + +func (s *snapshot) Histograms() map[string]HistogramSnapshot { + return s.histograms +} + +type counterSnapshot struct { + name string + tags map[string]string + value int64 +} + +func (s *counterSnapshot) Name() string { + return s.name +} + +func (s *counterSnapshot) Tags() map[string]string { + return s.tags +} + +func (s *counterSnapshot) Value() int64 { + return s.value +} + +type gaugeSnapshot struct { + name string + tags map[string]string + value float64 +} + +func (s *gaugeSnapshot) Name() string { + return s.name +} + +func (s *gaugeSnapshot) Tags() map[string]string { + return s.tags +} + +func (s *gaugeSnapshot) Value() float64 { + return s.value +} + +type timerSnapshot struct { + name string + tags map[string]string + values []time.Duration +} + +func (s *timerSnapshot) Name() string { + return s.name +} + +func (s *timerSnapshot) Tags() map[string]string { + return s.tags +} + +func (s *timerSnapshot) Values() []time.Duration { + return s.values +} + +type histogramSnapshot struct { + name string + tags map[string]string + values map[float64]int64 + durations map[time.Duration]int64 +} + +func (s *histogramSnapshot) Name() string { + return s.name +} + +func (s *histogramSnapshot) Tags() map[string]string { + return s.tags +} + +func (s *histogramSnapshot) Values() map[float64]int64 { + return s.values +} + +func (s *histogramSnapshot) Durations() map[time.Duration]int64 { + return s.durations +} diff --git a/vendor/github.com/uber-go/tally/v4/scope_registry.go b/vendor/github.com/uber-go/tally/v4/scope_registry.go new file mode 100644 index 0000000..57904c0 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/scope_registry.go @@ -0,0 +1,344 @@ +// Copyright (c) 2024 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tally + +import ( + "hash/maphash" + "runtime" + "sync" + "unsafe" + + "go.uber.org/atomic" +) + +var ( + scopeRegistryKey = keyForPrefixedStringMaps + + // Metrics related. + counterCardinalityName = "tally.internal.counter_cardinality" + gaugeCardinalityName = "tally.internal.gauge_cardinality" + histogramCardinalityName = "tally.internal.histogram_cardinality" + scopeCardinalityName = "tally.internal.num_active_scopes" +) + +const ( + // DefaultTagRedactValue is the default tag value to use when redacting + DefaultTagRedactValue = "global" +) + +type scopeRegistry struct { + seed maphash.Seed + root *scope + // We need a subscope per GOPROC so that we can take advantage of all the cpu available to the application. + subscopes []*scopeBucket + // Internal metrics related. + omitCardinalityMetrics bool + cardinalityMetricsTags map[string]string + sanitizedCounterCardinalityName string + sanitizedGaugeCardinalityName string + sanitizedHistogramCardinalityName string + sanitizedScopeCardinalityName string + cachedCounterCardinalityGauge CachedGauge + cachedGaugeCardinalityGauge CachedGauge + cachedHistogramCardinalityGauge CachedGauge + cachedScopeCardinalityGauge CachedGauge +} + +type scopeBucket struct { + mu sync.RWMutex + s map[string]*scope +} + +func newScopeRegistryWithShardCount( + root *scope, + shardCount uint, + omitCardinalityMetrics bool, + cardinalityMetricsTags map[string]string, +) *scopeRegistry { + if shardCount == 0 { + shardCount = uint(runtime.GOMAXPROCS(-1)) + } + + r := &scopeRegistry{ + root: root, + subscopes: make([]*scopeBucket, shardCount), + seed: maphash.MakeSeed(), + omitCardinalityMetrics: omitCardinalityMetrics, + sanitizedCounterCardinalityName: root.sanitizer.Name(counterCardinalityName), + sanitizedGaugeCardinalityName: root.sanitizer.Name(gaugeCardinalityName), + sanitizedHistogramCardinalityName: root.sanitizer.Name(histogramCardinalityName), + sanitizedScopeCardinalityName: root.sanitizer.Name(scopeCardinalityName), + cardinalityMetricsTags: map[string]string{ + "version": Version, + "host": DefaultTagRedactValue, + "instance": DefaultTagRedactValue, + }, + } + + for k, v := range cardinalityMetricsTags { + r.cardinalityMetricsTags[root.sanitizer.Key(k)] = root.sanitizer.Value(v) + } + + for i := uint(0); i < shardCount; i++ { + r.subscopes[i] = &scopeBucket{ + s: make(map[string]*scope), + } + r.subscopes[i].s[scopeRegistryKey(root.prefix, root.tags)] = root + } + if r.root.cachedReporter != nil && !omitCardinalityMetrics { + r.cachedCounterCardinalityGauge = r.root.cachedReporter.AllocateGauge(r.sanitizedCounterCardinalityName, r.cardinalityMetricsTags) + r.cachedGaugeCardinalityGauge = r.root.cachedReporter.AllocateGauge(r.sanitizedGaugeCardinalityName, r.cardinalityMetricsTags) + r.cachedHistogramCardinalityGauge = r.root.cachedReporter.AllocateGauge(r.sanitizedHistogramCardinalityName, r.cardinalityMetricsTags) + r.cachedScopeCardinalityGauge = r.root.cachedReporter.AllocateGauge(r.sanitizedScopeCardinalityName, r.cardinalityMetricsTags) + } + return r +} + +func (r *scopeRegistry) Report(reporter StatsReporter) { + defer r.purgeIfRootClosed() + r.reportInternalMetrics() + + for _, subscopeBucket := range r.subscopes { + subscopeBucket.mu.RLock() + + for name, s := range subscopeBucket.s { + s.report(reporter) + + if s.closed.Load() { + r.removeWithRLock(subscopeBucket, name) + s.clearMetrics() + } + } + + subscopeBucket.mu.RUnlock() + } +} + +func (r *scopeRegistry) CachedReport() { + defer r.purgeIfRootClosed() + r.reportInternalMetrics() + + for _, subscopeBucket := range r.subscopes { + subscopeBucket.mu.RLock() + + for name, s := range subscopeBucket.s { + s.cachedReport() + + if s.closed.Load() { + r.removeWithRLock(subscopeBucket, name) + s.clearMetrics() + } + } + + subscopeBucket.mu.RUnlock() + } +} + +func (r *scopeRegistry) ForEachScope(f func(*scope)) { + for _, subscopeBucket := range r.subscopes { + subscopeBucket.mu.RLock() + for _, s := range subscopeBucket.s { + f(s) + } + subscopeBucket.mu.RUnlock() + } +} + +func (r *scopeRegistry) Subscope(parent *scope, prefix string, tags map[string]string) *scope { + if r.root.closed.Load() || parent.closed.Load() { + return NoopScope.(*scope) + } + + var ( + buf = keyForPrefixedStringMapsAsKey(make([]byte, 0, 256), prefix, parent.tags, tags) + h maphash.Hash + ) + + h.SetSeed(r.seed) + _, _ = h.Write(buf) + subscopeBucket := r.subscopes[h.Sum64()%uint64(len(r.subscopes))] + + subscopeBucket.mu.RLock() + // buf is stack allocated and casting it to a string for lookup from the cache + // as the memory layout of []byte is a superset of string the below casting is safe and does not do any alloc + // However it cannot be used outside of the stack; a heap allocation is needed if that string needs to be stored + // in the map as a key + var ( + unsanitizedKey = *(*string)(unsafe.Pointer(&buf)) + sanitizedKey string + ) + + s, ok := r.lockedLookup(subscopeBucket, unsanitizedKey) + if ok { + // If this subscope isn't closed or is a test scope, return it. + // Otherwise, report it immediately and delete it so that a new + // (functional) scope can be returned instead. + if !s.closed.Load() || s.testScope { + subscopeBucket.mu.RUnlock() + return s + } + + switch { + case parent.reporter != nil: + s.report(parent.reporter) + case parent.cachedReporter != nil: + s.cachedReport() + } + } + + tags = parent.copyAndSanitizeMap(tags) + sanitizedKey = scopeRegistryKey(prefix, parent.tags, tags) + + // If a scope was found above but we didn't return, we need to remove the + // scope from both keys. + if ok { + r.removeWithRLock(subscopeBucket, unsanitizedKey) + r.removeWithRLock(subscopeBucket, sanitizedKey) + s.clearMetrics() + } + + subscopeBucket.mu.RUnlock() + + // Force-allocate the unsafe string as a safe string. Note that neither + // string(x) nor x+"" will have the desired effect (the former is a nop, + // and the latter will likely be elided), so append a new character and + // truncate instead. + // + // ref: https://go.dev/play/p/sxhExUKSxCw + unsanitizedKey = (unsanitizedKey + ".")[:len(unsanitizedKey)] + + subscopeBucket.mu.Lock() + defer subscopeBucket.mu.Unlock() + + if s, ok := r.lockedLookup(subscopeBucket, sanitizedKey); ok { + if _, ok = r.lockedLookup(subscopeBucket, unsanitizedKey); !ok { + subscopeBucket.s[unsanitizedKey] = s + } + return s + } + + allTags := mergeRightTags(parent.tags, tags) + subscope := &scope{ + separator: parent.separator, + prefix: prefix, + // NB(prateek): don't need to copy the tags here, + // we assume the map provided is immutable. + tags: allTags, + reporter: parent.reporter, + cachedReporter: parent.cachedReporter, + baseReporter: parent.baseReporter, + defaultBuckets: parent.defaultBuckets, + sanitizer: parent.sanitizer, + registry: parent.registry, + + counters: make(map[string]*counter), + countersSlice: make([]*counter, 0, _defaultInitialSliceSize), + gauges: make(map[string]*gauge), + gaugesSlice: make([]*gauge, 0, _defaultInitialSliceSize), + histograms: make(map[string]*histogram), + histogramsSlice: make([]*histogram, 0, _defaultInitialSliceSize), + timers: make(map[string]*timer), + bucketCache: parent.bucketCache, + done: make(chan struct{}), + testScope: parent.testScope, + } + subscopeBucket.s[sanitizedKey] = subscope + if _, ok := r.lockedLookup(subscopeBucket, unsanitizedKey); !ok { + subscopeBucket.s[unsanitizedKey] = subscope + } + return subscope +} + +func (r *scopeRegistry) lockedLookup(subscopeBucket *scopeBucket, key string) (*scope, bool) { + ss, ok := subscopeBucket.s[key] + return ss, ok +} + +func (r *scopeRegistry) purgeIfRootClosed() { + if !r.root.closed.Load() { + return + } + + for _, subscopeBucket := range r.subscopes { + subscopeBucket.mu.Lock() + for k, s := range subscopeBucket.s { + _ = s.Close() + s.clearMetrics() + delete(subscopeBucket.s, k) + } + subscopeBucket.mu.Unlock() + } +} + +func (r *scopeRegistry) removeWithRLock(subscopeBucket *scopeBucket, key string) { + // n.b. This function must lock the registry for writing and return it to an + // RLocked state prior to exiting. Defer order is important (LIFO). + subscopeBucket.mu.RUnlock() + defer subscopeBucket.mu.RLock() + subscopeBucket.mu.Lock() + defer subscopeBucket.mu.Unlock() + delete(subscopeBucket.s, key) +} + +// Records internal Metrics' cardinalities. +func (r *scopeRegistry) reportInternalMetrics() { + if r.omitCardinalityMetrics { + return + } + + counters, gauges, histograms, scopes := atomic.Int64{}, atomic.Int64{}, atomic.Int64{}, atomic.Int64{} + rootCounters, rootGauges, rootHistograms := atomic.Int64{}, atomic.Int64{}, atomic.Int64{} + scopes.Inc() // Account for root scope. + r.ForEachScope( + func(ss *scope) { + ss.cm.RLock() + defer ss.cm.RUnlock() + counterSliceLen, gaugeSliceLen, histogramSliceLen := int64(len(ss.countersSlice)), int64(len(ss.gaugesSlice)), int64(len(ss.histogramsSlice)) + if ss.root { // Root scope is referenced across all buckets. + rootCounters.Store(counterSliceLen) + rootGauges.Store(gaugeSliceLen) + rootHistograms.Store(histogramSliceLen) + return + } + counters.Add(counterSliceLen) + gauges.Add(gaugeSliceLen) + histograms.Add(histogramSliceLen) + scopes.Inc() + }, + ) + + counters.Add(rootCounters.Load()) + gauges.Add(rootGauges.Load()) + histograms.Add(rootHistograms.Load()) + if r.root.reporter != nil { + r.root.reporter.ReportGauge(r.sanitizedCounterCardinalityName, r.cardinalityMetricsTags, float64(counters.Load())) + r.root.reporter.ReportGauge(r.sanitizedGaugeCardinalityName, r.cardinalityMetricsTags, float64(gauges.Load())) + r.root.reporter.ReportGauge(r.sanitizedHistogramCardinalityName, r.cardinalityMetricsTags, float64(histograms.Load())) + r.root.reporter.ReportGauge(r.sanitizedScopeCardinalityName, r.cardinalityMetricsTags, float64(scopes.Load())) + } + + if r.root.cachedReporter != nil { + r.cachedCounterCardinalityGauge.ReportGauge(float64(counters.Load())) + r.cachedGaugeCardinalityGauge.ReportGauge(float64(gauges.Load())) + r.cachedHistogramCardinalityGauge.ReportGauge(float64(histograms.Load())) + r.cachedScopeCardinalityGauge.ReportGauge(float64(scopes.Load())) + } +} diff --git a/vendor/github.com/uber-go/tally/v4/stats.go b/vendor/github.com/uber-go/tally/v4/stats.go new file mode 100644 index 0000000..204c306 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/stats.go @@ -0,0 +1,563 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tally + +import ( + "fmt" + "math" + "sort" + "sync" + "sync/atomic" + "time" + + "github.com/uber-go/tally/v4/internal/identity" +) + +var ( + capabilitiesNone = &capabilities{ + reporting: false, + tagging: false, + } + capabilitiesReportingNoTagging = &capabilities{ + reporting: true, + tagging: false, + } + capabilitiesReportingTagging = &capabilities{ + reporting: true, + tagging: true, + } +) + +type capabilities struct { + reporting bool + tagging bool +} + +func (c *capabilities) Reporting() bool { + return c.reporting +} + +func (c *capabilities) Tagging() bool { + return c.tagging +} + +type counter struct { + prev int64 + curr int64 + cachedCount CachedCount +} + +func newCounter(cachedCount CachedCount) *counter { + return &counter{cachedCount: cachedCount} +} + +func (c *counter) Inc(v int64) { + atomic.AddInt64(&c.curr, v) +} + +func (c *counter) value() int64 { + curr := atomic.LoadInt64(&c.curr) + + prev := atomic.LoadInt64(&c.prev) + if prev == curr { + return 0 + } + atomic.StoreInt64(&c.prev, curr) + return curr - prev +} + +func (c *counter) report(name string, tags map[string]string, r StatsReporter) { + delta := c.value() + if delta == 0 { + return + } + + r.ReportCounter(name, tags, delta) +} + +func (c *counter) cachedReport() { + delta := c.value() + if delta == 0 { + return + } + + c.cachedCount.ReportCount(delta) +} + +func (c *counter) snapshot() int64 { + return atomic.LoadInt64(&c.curr) - atomic.LoadInt64(&c.prev) +} + +type gauge struct { + updated uint64 + curr uint64 + cachedGauge CachedGauge +} + +func newGauge(cachedGauge CachedGauge) *gauge { + return &gauge{cachedGauge: cachedGauge} +} + +func (g *gauge) Update(v float64) { + atomic.StoreUint64(&g.curr, math.Float64bits(v)) + atomic.StoreUint64(&g.updated, 1) +} + +func (g *gauge) value() float64 { + return math.Float64frombits(atomic.LoadUint64(&g.curr)) +} + +func (g *gauge) report(name string, tags map[string]string, r StatsReporter) { + if atomic.SwapUint64(&g.updated, 0) == 1 { + r.ReportGauge(name, tags, g.value()) + } +} + +func (g *gauge) cachedReport() { + if atomic.SwapUint64(&g.updated, 0) == 1 { + g.cachedGauge.ReportGauge(g.value()) + } +} + +func (g *gauge) snapshot() float64 { + return math.Float64frombits(atomic.LoadUint64(&g.curr)) +} + +// NB(jra3): timers are a little special because they do no aggregate any data +// at the timer level. The reporter buffers may timer entries and periodically +// flushes. +type timer struct { + name string + tags map[string]string + reporter StatsReporter + cachedTimer CachedTimer + unreported timerValues +} + +type timerValues struct { + sync.RWMutex + values []time.Duration +} + +func newTimer( + name string, + tags map[string]string, + r StatsReporter, + cachedTimer CachedTimer, +) *timer { + t := &timer{ + name: name, + tags: tags, + reporter: r, + cachedTimer: cachedTimer, + } + if r == nil { + t.reporter = &timerNoReporterSink{timer: t} + } + return t +} + +func (t *timer) Record(interval time.Duration) { + if t.cachedTimer != nil { + t.cachedTimer.ReportTimer(interval) + } else { + t.reporter.ReportTimer(t.name, t.tags, interval) + } +} + +func (t *timer) Start() Stopwatch { + return NewStopwatch(globalNow(), t) +} + +func (t *timer) RecordStopwatch(stopwatchStart time.Time) { + d := globalNow().Sub(stopwatchStart) + t.Record(d) +} + +func (t *timer) snapshot() []time.Duration { + t.unreported.RLock() + snap := make([]time.Duration, len(t.unreported.values)) + copy(snap, t.unreported.values) + t.unreported.RUnlock() + return snap +} + +type timerNoReporterSink struct { + sync.RWMutex + timer *timer +} + +func (r *timerNoReporterSink) ReportCounter( + name string, + tags map[string]string, + value int64, +) { +} + +func (r *timerNoReporterSink) ReportGauge( + name string, + tags map[string]string, + value float64, +) { +} + +func (r *timerNoReporterSink) ReportTimer( + name string, + tags map[string]string, + interval time.Duration, +) { + r.timer.unreported.Lock() + r.timer.unreported.values = append(r.timer.unreported.values, interval) + r.timer.unreported.Unlock() +} + +func (r *timerNoReporterSink) ReportHistogramValueSamples( + name string, + tags map[string]string, + buckets Buckets, + bucketLowerBound float64, + bucketUpperBound float64, + samples int64, +) { +} + +func (r *timerNoReporterSink) ReportHistogramDurationSamples( + name string, + tags map[string]string, + buckets Buckets, + bucketLowerBound time.Duration, + bucketUpperBound time.Duration, + samples int64, +) { +} + +func (r *timerNoReporterSink) Capabilities() Capabilities { + return capabilitiesReportingTagging +} + +func (r *timerNoReporterSink) Flush() { +} + +type sampleCounter struct { + counter *counter + cachedBucket CachedHistogramBucket +} + +type histogram struct { + htype histogramType + name string + tags map[string]string + reporter StatsReporter + specification Buckets + buckets []histogramBucket + samples []sampleCounter +} + +type histogramType int + +const ( + valueHistogramType histogramType = iota + durationHistogramType +) + +func newHistogram( + htype histogramType, + name string, + tags map[string]string, + reporter StatsReporter, + storage bucketStorage, + cachedHistogram CachedHistogram, +) *histogram { + h := &histogram{ + htype: htype, + name: name, + tags: tags, + reporter: reporter, + specification: storage.buckets, + buckets: storage.hbuckets, + samples: make([]sampleCounter, len(storage.hbuckets)), + } + + for i := range h.samples { + h.samples[i].counter = newCounter(nil) + + if cachedHistogram != nil { + switch htype { + case durationHistogramType: + h.samples[i].cachedBucket = cachedHistogram.DurationBucket( + durationLowerBound(storage.hbuckets, i), + storage.hbuckets[i].durationUpperBound, + ) + case valueHistogramType: + h.samples[i].cachedBucket = cachedHistogram.ValueBucket( + valueLowerBound(storage.hbuckets, i), + storage.hbuckets[i].valueUpperBound, + ) + } + } + } + + return h +} + +func (h *histogram) report(name string, tags map[string]string, r StatsReporter) { + for i := range h.buckets { + samples := h.samples[i].counter.value() + if samples == 0 { + continue + } + + switch h.htype { + case valueHistogramType: + r.ReportHistogramValueSamples( + name, + tags, + h.specification, + valueLowerBound(h.buckets, i), + h.buckets[i].valueUpperBound, + samples, + ) + case durationHistogramType: + r.ReportHistogramDurationSamples( + name, + tags, + h.specification, + durationLowerBound(h.buckets, i), + h.buckets[i].durationUpperBound, + samples, + ) + } + } +} + +func (h *histogram) cachedReport() { + for i := range h.buckets { + samples := h.samples[i].counter.value() + if samples == 0 { + continue + } + + switch h.htype { + case valueHistogramType: + h.samples[i].cachedBucket.ReportSamples(samples) + case durationHistogramType: + h.samples[i].cachedBucket.ReportSamples(samples) + } + } +} + +func (h *histogram) RecordValue(value float64) { + if h.htype != valueHistogramType { + return + } + + // Find the highest inclusive of the bucket upper bound + // and emit directly to it. Since we use BucketPairs to derive + // buckets there will always be an inclusive bucket as + // we always have a math.MaxFloat64 bucket. + idx := sort.Search(len(h.buckets), func(i int) bool { + return h.buckets[i].valueUpperBound >= value + }) + h.samples[idx].counter.Inc(1) +} + +func (h *histogram) RecordDuration(value time.Duration) { + if h.htype != durationHistogramType { + return + } + + // Find the highest inclusive of the bucket upper bound + // and emit directly to it. Since we use BucketPairs to derive + // buckets there will always be an inclusive bucket as + // we always have a math.MaxInt64 bucket. + idx := sort.Search(len(h.buckets), func(i int) bool { + return h.buckets[i].durationUpperBound >= value + }) + h.samples[idx].counter.Inc(1) +} + +func (h *histogram) Start() Stopwatch { + return NewStopwatch(globalNow(), h) +} + +func (h *histogram) RecordStopwatch(stopwatchStart time.Time) { + d := globalNow().Sub(stopwatchStart) + h.RecordDuration(d) +} + +func (h *histogram) snapshotValues() map[float64]int64 { + if h.htype != valueHistogramType { + return nil + } + + vals := make(map[float64]int64, len(h.buckets)) + for i := range h.buckets { + vals[h.buckets[i].valueUpperBound] = h.samples[i].counter.snapshot() + } + + return vals +} + +func (h *histogram) snapshotDurations() map[time.Duration]int64 { + if h.htype != durationHistogramType { + return nil + } + + durations := make(map[time.Duration]int64, len(h.buckets)) + for i := range h.buckets { + durations[h.buckets[i].durationUpperBound] = h.samples[i].counter.snapshot() + } + + return durations +} + +type histogramBucket struct { + valueUpperBound float64 + durationUpperBound time.Duration +} + +func durationLowerBound(buckets []histogramBucket, i int) time.Duration { + if i <= 0 { + return time.Duration(math.MinInt64) + } + return buckets[i-1].durationUpperBound +} + +func valueLowerBound(buckets []histogramBucket, i int) float64 { + if i <= 0 { + return -math.MaxFloat64 + } + return buckets[i-1].valueUpperBound +} + +type bucketStorage struct { + buckets Buckets + hbuckets []histogramBucket +} + +func newBucketStorage( + htype histogramType, + buckets Buckets, +) bucketStorage { + var ( + pairs = BucketPairs(buckets) + storage = bucketStorage{ + buckets: buckets, + hbuckets: make([]histogramBucket, 0, len(pairs)), + } + ) + + for _, pair := range pairs { + storage.hbuckets = append(storage.hbuckets, histogramBucket{ + valueUpperBound: pair.UpperBoundValue(), + durationUpperBound: pair.UpperBoundDuration(), + }) + } + + return storage +} + +type bucketCache struct { + mtx sync.RWMutex + cache map[uint64]bucketStorage +} + +func newBucketCache() *bucketCache { + return &bucketCache{ + cache: make(map[uint64]bucketStorage), + } +} + +func (c *bucketCache) Get( + htype histogramType, + buckets Buckets, +) bucketStorage { + id := getBucketsIdentity(buckets) + + c.mtx.RLock() + storage, ok := c.cache[id] + if !ok { + c.mtx.RUnlock() + c.mtx.Lock() + storage = newBucketStorage(htype, buckets) + c.cache[id] = storage + c.mtx.Unlock() + } else { + c.mtx.RUnlock() + if !bucketsEqual(buckets, storage.buckets) { + storage = newBucketStorage(htype, buckets) + } + } + + return storage +} + +// NullStatsReporter is an implementation of StatsReporter than simply does nothing. +var NullStatsReporter StatsReporter = nullStatsReporter{} + +func (r nullStatsReporter) ReportCounter(name string, tags map[string]string, value int64) { +} + +func (r nullStatsReporter) ReportGauge(name string, tags map[string]string, value float64) { +} + +func (r nullStatsReporter) ReportTimer(name string, tags map[string]string, interval time.Duration) { +} + +func (r nullStatsReporter) ReportHistogramValueSamples( + name string, + tags map[string]string, + buckets Buckets, + bucketLowerBound, + bucketUpperBound float64, + samples int64, +) { +} + +func (r nullStatsReporter) ReportHistogramDurationSamples( + name string, + tags map[string]string, + buckets Buckets, + bucketLowerBound, + bucketUpperBound time.Duration, + samples int64, +) { +} + +func (r nullStatsReporter) Capabilities() Capabilities { + return capabilitiesNone +} + +func (r nullStatsReporter) Flush() { +} + +type nullStatsReporter struct{} + +func getBucketsIdentity(buckets Buckets) uint64 { + switch b := buckets.(type) { + case DurationBuckets: + return identity.Durations(b.AsDurations()) + case ValueBuckets: + return identity.Float64s(b.AsValues()) + default: + panic(fmt.Sprintf("unexpected bucket type: %T", b)) + } +} diff --git a/vendor/github.com/uber-go/tally/v4/types.go b/vendor/github.com/uber-go/tally/v4/types.go new file mode 100644 index 0000000..1a15971 --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/types.go @@ -0,0 +1,157 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tally + +import ( + "fmt" + "sort" + "time" +) + +// Scope is a namespace wrapper around a stats reporter, ensuring that +// all emitted values have a given prefix or set of tags. +// +// IMPORTANT: When using Prometheus reporters, users must take care to +// not create metrics from both parent scopes and subscopes +// that have the same metric name but different tag keys, +// as metric allocation will panic. +type Scope interface { + // Counter returns the Counter object corresponding to the name. + Counter(name string) Counter + + // Gauge returns the Gauge object corresponding to the name. + Gauge(name string) Gauge + + // Timer returns the Timer object corresponding to the name. + Timer(name string) Timer + + // Histogram returns the Histogram object corresponding to the name. + // To use default value and duration buckets configured for the scope + // simply pass tally.DefaultBuckets or nil. + // You can use tally.ValueBuckets{x, y, ...} for value buckets. + // You can use tally.DurationBuckets{x, y, ...} for duration buckets. + // You can use tally.MustMakeLinearValueBuckets(start, width, count) for linear values. + // You can use tally.MustMakeLinearDurationBuckets(start, width, count) for linear durations. + // You can use tally.MustMakeExponentialValueBuckets(start, factor, count) for exponential values. + // You can use tally.MustMakeExponentialDurationBuckets(start, factor, count) for exponential durations. + Histogram(name string, buckets Buckets) Histogram + + // Tagged returns a new child scope with the given tags and current tags. + Tagged(tags map[string]string) Scope + + // SubScope returns a new child scope appending a further name prefix. + SubScope(name string) Scope + + // Capabilities returns a description of metrics reporting capabilities. + Capabilities() Capabilities +} + +// Counter is the interface for emitting counter type metrics. +type Counter interface { + // Inc increments the counter by a delta. + Inc(delta int64) +} + +// Gauge is the interface for emitting gauge metrics. +type Gauge interface { + // Update sets the gauges absolute value. + Update(value float64) +} + +// Timer is the interface for emitting timer metrics. +type Timer interface { + // Record a specific duration directly. + Record(value time.Duration) + + // Start gives you back a specific point in time to report via Stop. + Start() Stopwatch +} + +// Histogram is the interface for emitting histogram metrics +type Histogram interface { + // RecordValue records a specific value directly. + // Will use the configured value buckets for the histogram. + RecordValue(value float64) + + // RecordDuration records a specific duration directly. + // Will use the configured duration buckets for the histogram. + RecordDuration(value time.Duration) + + // Start gives you a specific point in time to then record a duration. + // Will use the configured duration buckets for the histogram. + Start() Stopwatch +} + +// Stopwatch is a helper for simpler tracking of elapsed time, use the +// Stop() method to report time elapsed since its created back to the +// timer or histogram. +type Stopwatch struct { + start time.Time + recorder StopwatchRecorder +} + +// NewStopwatch creates a new immutable stopwatch for recording the start +// time to a stopwatch reporter. +func NewStopwatch(start time.Time, r StopwatchRecorder) Stopwatch { + return Stopwatch{start: start, recorder: r} +} + +// Stop reports time elapsed since the stopwatch start to the recorder. +func (sw Stopwatch) Stop() { + sw.recorder.RecordStopwatch(sw.start) +} + +// StopwatchRecorder is a recorder that is called when a stopwatch is +// stopped with Stop(). +type StopwatchRecorder interface { + RecordStopwatch(stopwatchStart time.Time) +} + +// Buckets is an interface that can represent a set of buckets +// either as float64s or as durations. +type Buckets interface { + fmt.Stringer + sort.Interface + + // AsValues returns a representation of the buckets as float64s + AsValues() []float64 + + // AsDurations returns a representation of the buckets as time.Durations + AsDurations() []time.Duration +} + +// BucketPair describes the lower and upper bounds +// for a derived bucket from a buckets set. +type BucketPair interface { + LowerBoundValue() float64 + UpperBoundValue() float64 + LowerBoundDuration() time.Duration + UpperBoundDuration() time.Duration +} + +// Capabilities is a description of metrics reporting capabilities. +type Capabilities interface { + // Reporting returns whether the reporter has the ability to actively report. + Reporting() bool + + // Tagging returns whether the reporter has the capability for tagged metrics. + Tagging() bool +} diff --git a/vendor/github.com/uber-go/tally/v4/version.go b/vendor/github.com/uber-go/tally/v4/version.go new file mode 100644 index 0000000..847437c --- /dev/null +++ b/vendor/github.com/uber-go/tally/v4/version.go @@ -0,0 +1,24 @@ +// Copyright (c) 2024 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tally + +// Version is the current version of the library. +const Version = "4.1.16" diff --git a/vendor/go.uber.org/atomic/CHANGELOG.md b/vendor/go.uber.org/atomic/CHANGELOG.md index 38f564e..6f87f33 100644 --- a/vendor/go.uber.org/atomic/CHANGELOG.md +++ b/vendor/go.uber.org/atomic/CHANGELOG.md @@ -4,6 +4,33 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.11.0] - 2023-05-02 +### Fixed +- Fix initialization of `Value` wrappers. + +### Added +- Add `String` method to `atomic.Pointer[T]` type allowing users to safely print +underlying values of pointers. + +[1.11.0]: https://github.com/uber-go/atomic/compare/v1.10.0...v1.11.0 + +## [1.10.0] - 2022-08-11 +### Added +- Add `atomic.Float32` type for atomic operations on `float32`. +- Add `CompareAndSwap` and `Swap` methods to `atomic.String`, `atomic.Error`, + and `atomic.Value`. +- Add generic `atomic.Pointer[T]` type for atomic operations on pointers of any + type. This is present only for Go 1.18 or higher, and is a drop-in for + replacement for the standard library's `sync/atomic.Pointer` type. + +### Changed +- Deprecate `CAS` methods on all types in favor of corresponding + `CompareAndSwap` methods. + +Thanks to @eNV25 and @icpd for their contributions to this release. + +[1.10.0]: https://github.com/uber-go/atomic/compare/v1.9.0...v1.10.0 + ## [1.9.0] - 2021-07-15 ### Added - Add `Float64.Swap` to match int atomic operations. diff --git a/vendor/go.uber.org/atomic/bool.go b/vendor/go.uber.org/atomic/bool.go index 209df7b..f0a2ddd 100644 --- a/vendor/go.uber.org/atomic/bool.go +++ b/vendor/go.uber.org/atomic/bool.go @@ -1,6 +1,6 @@ // @generated Code generated by gen-atomicwrapper. -// Copyright (c) 2020-2021 Uber Technologies, Inc. +// Copyright (c) 2020-2023 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -55,8 +55,15 @@ func (x *Bool) Store(val bool) { } // CAS is an atomic compare-and-swap for bool values. +// +// Deprecated: Use CompareAndSwap. func (x *Bool) CAS(old, new bool) (swapped bool) { - return x.v.CAS(boolToInt(old), boolToInt(new)) + return x.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap for bool values. +func (x *Bool) CompareAndSwap(old, new bool) (swapped bool) { + return x.v.CompareAndSwap(boolToInt(old), boolToInt(new)) } // Swap atomically stores the given bool and returns the old diff --git a/vendor/go.uber.org/atomic/duration.go b/vendor/go.uber.org/atomic/duration.go index 207594f..7c23868 100644 --- a/vendor/go.uber.org/atomic/duration.go +++ b/vendor/go.uber.org/atomic/duration.go @@ -1,6 +1,6 @@ // @generated Code generated by gen-atomicwrapper. -// Copyright (c) 2020-2021 Uber Technologies, Inc. +// Copyright (c) 2020-2023 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -56,8 +56,15 @@ func (x *Duration) Store(val time.Duration) { } // CAS is an atomic compare-and-swap for time.Duration values. +// +// Deprecated: Use CompareAndSwap. func (x *Duration) CAS(old, new time.Duration) (swapped bool) { - return x.v.CAS(int64(old), int64(new)) + return x.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap for time.Duration values. +func (x *Duration) CompareAndSwap(old, new time.Duration) (swapped bool) { + return x.v.CompareAndSwap(int64(old), int64(new)) } // Swap atomically stores the given time.Duration and returns the old diff --git a/vendor/go.uber.org/atomic/error.go b/vendor/go.uber.org/atomic/error.go index 3be19c3..b7e3f12 100644 --- a/vendor/go.uber.org/atomic/error.go +++ b/vendor/go.uber.org/atomic/error.go @@ -1,6 +1,6 @@ // @generated Code generated by gen-atomicwrapper. -// Copyright (c) 2020-2021 Uber Technologies, Inc. +// Copyright (c) 2020-2023 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -49,3 +49,24 @@ func (x *Error) Load() error { func (x *Error) Store(val error) { x.v.Store(packError(val)) } + +// CompareAndSwap is an atomic compare-and-swap for error values. +func (x *Error) CompareAndSwap(old, new error) (swapped bool) { + if x.v.CompareAndSwap(packError(old), packError(new)) { + return true + } + + if old == _zeroError { + // If the old value is the empty value, then it's possible the + // underlying Value hasn't been set and is nil, so retry with nil. + return x.v.CompareAndSwap(nil, packError(new)) + } + + return false +} + +// Swap atomically stores the given error and returns the old +// value. +func (x *Error) Swap(val error) (old error) { + return unpackError(x.v.Swap(packError(val))) +} diff --git a/vendor/go.uber.org/atomic/error_ext.go b/vendor/go.uber.org/atomic/error_ext.go index ffe0be2..d31fb63 100644 --- a/vendor/go.uber.org/atomic/error_ext.go +++ b/vendor/go.uber.org/atomic/error_ext.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 Uber Technologies, Inc. +// Copyright (c) 2020-2022 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ package atomic // atomic.Value panics on nil inputs, or if the underlying type changes. // Stabilize by always storing a custom struct that we control. -//go:generate bin/gen-atomicwrapper -name=Error -type=error -wrapped=Value -pack=packError -unpack=unpackError -file=error.go +//go:generate bin/gen-atomicwrapper -name=Error -type=error -wrapped=Value -pack=packError -unpack=unpackError -compareandswap -swap -file=error.go type packedError struct{ Value error } diff --git a/vendor/go.uber.org/atomic/float32.go b/vendor/go.uber.org/atomic/float32.go new file mode 100644 index 0000000..62c3633 --- /dev/null +++ b/vendor/go.uber.org/atomic/float32.go @@ -0,0 +1,77 @@ +// @generated Code generated by gen-atomicwrapper. + +// Copyright (c) 2020-2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "encoding/json" + "math" +) + +// Float32 is an atomic type-safe wrapper for float32 values. +type Float32 struct { + _ nocmp // disallow non-atomic comparison + + v Uint32 +} + +var _zeroFloat32 float32 + +// NewFloat32 creates a new Float32. +func NewFloat32(val float32) *Float32 { + x := &Float32{} + if val != _zeroFloat32 { + x.Store(val) + } + return x +} + +// Load atomically loads the wrapped float32. +func (x *Float32) Load() float32 { + return math.Float32frombits(x.v.Load()) +} + +// Store atomically stores the passed float32. +func (x *Float32) Store(val float32) { + x.v.Store(math.Float32bits(val)) +} + +// Swap atomically stores the given float32 and returns the old +// value. +func (x *Float32) Swap(val float32) (old float32) { + return math.Float32frombits(x.v.Swap(math.Float32bits(val))) +} + +// MarshalJSON encodes the wrapped float32 into JSON. +func (x *Float32) MarshalJSON() ([]byte, error) { + return json.Marshal(x.Load()) +} + +// UnmarshalJSON decodes a float32 from JSON. +func (x *Float32) UnmarshalJSON(b []byte) error { + var v float32 + if err := json.Unmarshal(b, &v); err != nil { + return err + } + x.Store(v) + return nil +} diff --git a/vendor/go.uber.org/atomic/float32_ext.go b/vendor/go.uber.org/atomic/float32_ext.go new file mode 100644 index 0000000..b0cd8d9 --- /dev/null +++ b/vendor/go.uber.org/atomic/float32_ext.go @@ -0,0 +1,76 @@ +// Copyright (c) 2020-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package atomic + +import ( + "math" + "strconv" +) + +//go:generate bin/gen-atomicwrapper -name=Float32 -type=float32 -wrapped=Uint32 -pack=math.Float32bits -unpack=math.Float32frombits -swap -json -imports math -file=float32.go + +// Add atomically adds to the wrapped float32 and returns the new value. +func (f *Float32) Add(delta float32) float32 { + for { + old := f.Load() + new := old + delta + if f.CAS(old, new) { + return new + } + } +} + +// Sub atomically subtracts from the wrapped float32 and returns the new value. +func (f *Float32) Sub(delta float32) float32 { + return f.Add(-delta) +} + +// CAS is an atomic compare-and-swap for float32 values. +// +// Deprecated: Use CompareAndSwap +func (f *Float32) CAS(old, new float32) (swapped bool) { + return f.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap for float32 values. +// +// Note: CompareAndSwap handles NaN incorrectly. NaN != NaN using Go's inbuilt operators +// but CompareAndSwap allows a stored NaN to compare equal to a passed in NaN. +// This avoids typical CompareAndSwap loops from blocking forever, e.g., +// +// for { +// old := atom.Load() +// new = f(old) +// if atom.CompareAndSwap(old, new) { +// break +// } +// } +// +// If CompareAndSwap did not match NaN to match, then the above would loop forever. +func (f *Float32) CompareAndSwap(old, new float32) (swapped bool) { + return f.v.CompareAndSwap(math.Float32bits(old), math.Float32bits(new)) +} + +// String encodes the wrapped value as a string. +func (f *Float32) String() string { + // 'g' is the behavior for floats with %v. + return strconv.FormatFloat(float64(f.Load()), 'g', -1, 32) +} diff --git a/vendor/go.uber.org/atomic/float64.go b/vendor/go.uber.org/atomic/float64.go index 8a13671..5bc11ca 100644 --- a/vendor/go.uber.org/atomic/float64.go +++ b/vendor/go.uber.org/atomic/float64.go @@ -1,6 +1,6 @@ // @generated Code generated by gen-atomicwrapper. -// Copyright (c) 2020-2021 Uber Technologies, Inc. +// Copyright (c) 2020-2023 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/go.uber.org/atomic/float64_ext.go b/vendor/go.uber.org/atomic/float64_ext.go index df36b01..48c52b0 100644 --- a/vendor/go.uber.org/atomic/float64_ext.go +++ b/vendor/go.uber.org/atomic/float64_ext.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 Uber Technologies, Inc. +// Copyright (c) 2020-2022 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -45,21 +45,28 @@ func (f *Float64) Sub(delta float64) float64 { // CAS is an atomic compare-and-swap for float64 values. // -// Note: CAS handles NaN incorrectly. NaN != NaN using Go's inbuilt operators -// but CAS allows a stored NaN to compare equal to a passed in NaN. -// This avoids typical CAS loops from blocking forever, e.g., +// Deprecated: Use CompareAndSwap +func (f *Float64) CAS(old, new float64) (swapped bool) { + return f.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap for float64 values. // -// for { -// old := atom.Load() -// new = f(old) -// if atom.CAS(old, new) { -// break -// } -// } +// Note: CompareAndSwap handles NaN incorrectly. NaN != NaN using Go's inbuilt operators +// but CompareAndSwap allows a stored NaN to compare equal to a passed in NaN. +// This avoids typical CompareAndSwap loops from blocking forever, e.g., // -// If CAS did not match NaN to match, then the above would loop forever. -func (f *Float64) CAS(old, new float64) (swapped bool) { - return f.v.CAS(math.Float64bits(old), math.Float64bits(new)) +// for { +// old := atom.Load() +// new = f(old) +// if atom.CompareAndSwap(old, new) { +// break +// } +// } +// +// If CompareAndSwap did not match NaN to match, then the above would loop forever. +func (f *Float64) CompareAndSwap(old, new float64) (swapped bool) { + return f.v.CompareAndSwap(math.Float64bits(old), math.Float64bits(new)) } // String encodes the wrapped value as a string. diff --git a/vendor/go.uber.org/atomic/int32.go b/vendor/go.uber.org/atomic/int32.go index 640ea36..5320eac 100644 --- a/vendor/go.uber.org/atomic/int32.go +++ b/vendor/go.uber.org/atomic/int32.go @@ -1,6 +1,6 @@ // @generated Code generated by gen-atomicint. -// Copyright (c) 2020-2021 Uber Technologies, Inc. +// Copyright (c) 2020-2023 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -66,7 +66,14 @@ func (i *Int32) Dec() int32 { } // CAS is an atomic compare-and-swap. +// +// Deprecated: Use CompareAndSwap. func (i *Int32) CAS(old, new int32) (swapped bool) { + return i.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (i *Int32) CompareAndSwap(old, new int32) (swapped bool) { return atomic.CompareAndSwapInt32(&i.v, old, new) } diff --git a/vendor/go.uber.org/atomic/int64.go b/vendor/go.uber.org/atomic/int64.go index 9ab66b9..460821d 100644 --- a/vendor/go.uber.org/atomic/int64.go +++ b/vendor/go.uber.org/atomic/int64.go @@ -1,6 +1,6 @@ // @generated Code generated by gen-atomicint. -// Copyright (c) 2020-2021 Uber Technologies, Inc. +// Copyright (c) 2020-2023 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -66,7 +66,14 @@ func (i *Int64) Dec() int64 { } // CAS is an atomic compare-and-swap. +// +// Deprecated: Use CompareAndSwap. func (i *Int64) CAS(old, new int64) (swapped bool) { + return i.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (i *Int64) CompareAndSwap(old, new int64) (swapped bool) { return atomic.CompareAndSwapInt64(&i.v, old, new) } diff --git a/vendor/go.uber.org/atomic/nocmp.go b/vendor/go.uber.org/atomic/nocmp.go index a8201cb..54b7417 100644 --- a/vendor/go.uber.org/atomic/nocmp.go +++ b/vendor/go.uber.org/atomic/nocmp.go @@ -23,13 +23,13 @@ package atomic // nocmp is an uncomparable struct. Embed this inside another struct to make // it uncomparable. // -// type Foo struct { -// nocmp -// // ... -// } +// type Foo struct { +// nocmp +// // ... +// } // // This DOES NOT: // -// - Disallow shallow copies of structs -// - Disallow comparison of pointers to uncomparable structs +// - Disallow shallow copies of structs +// - Disallow comparison of pointers to uncomparable structs type nocmp [0]func() diff --git a/vendor/go.uber.org/atomic/pointer_go118.go b/vendor/go.uber.org/atomic/pointer_go118.go new file mode 100644 index 0000000..1fb6c03 --- /dev/null +++ b/vendor/go.uber.org/atomic/pointer_go118.go @@ -0,0 +1,31 @@ +// Copyright (c) 2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build go1.18 +// +build go1.18 + +package atomic + +import "fmt" + +// String returns a human readable representation of a Pointer's underlying value. +func (p *Pointer[T]) String() string { + return fmt.Sprint(p.Load()) +} diff --git a/vendor/go.uber.org/atomic/pointer_go118_pre119.go b/vendor/go.uber.org/atomic/pointer_go118_pre119.go new file mode 100644 index 0000000..e0f47db --- /dev/null +++ b/vendor/go.uber.org/atomic/pointer_go118_pre119.go @@ -0,0 +1,60 @@ +// Copyright (c) 2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build go1.18 && !go1.19 +// +build go1.18,!go1.19 + +package atomic + +import "unsafe" + +type Pointer[T any] struct { + _ nocmp // disallow non-atomic comparison + p UnsafePointer +} + +// NewPointer creates a new Pointer. +func NewPointer[T any](v *T) *Pointer[T] { + var p Pointer[T] + if v != nil { + p.p.Store(unsafe.Pointer(v)) + } + return &p +} + +// Load atomically loads the wrapped value. +func (p *Pointer[T]) Load() *T { + return (*T)(p.p.Load()) +} + +// Store atomically stores the passed value. +func (p *Pointer[T]) Store(val *T) { + p.p.Store(unsafe.Pointer(val)) +} + +// Swap atomically swaps the wrapped pointer and returns the old value. +func (p *Pointer[T]) Swap(val *T) (old *T) { + return (*T)(p.p.Swap(unsafe.Pointer(val))) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (p *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool) { + return p.p.CompareAndSwap(unsafe.Pointer(old), unsafe.Pointer(new)) +} diff --git a/vendor/go.uber.org/atomic/pointer_go119.go b/vendor/go.uber.org/atomic/pointer_go119.go new file mode 100644 index 0000000..6726f17 --- /dev/null +++ b/vendor/go.uber.org/atomic/pointer_go119.go @@ -0,0 +1,61 @@ +// Copyright (c) 2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build go1.19 +// +build go1.19 + +package atomic + +import "sync/atomic" + +// Pointer is an atomic pointer of type *T. +type Pointer[T any] struct { + _ nocmp // disallow non-atomic comparison + p atomic.Pointer[T] +} + +// NewPointer creates a new Pointer. +func NewPointer[T any](v *T) *Pointer[T] { + var p Pointer[T] + if v != nil { + p.p.Store(v) + } + return &p +} + +// Load atomically loads the wrapped value. +func (p *Pointer[T]) Load() *T { + return p.p.Load() +} + +// Store atomically stores the passed value. +func (p *Pointer[T]) Store(val *T) { + p.p.Store(val) +} + +// Swap atomically swaps the wrapped pointer and returns the old value. +func (p *Pointer[T]) Swap(val *T) (old *T) { + return p.p.Swap(val) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (p *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool) { + return p.p.CompareAndSwap(old, new) +} diff --git a/vendor/go.uber.org/atomic/string.go b/vendor/go.uber.org/atomic/string.go index 80df93d..061466c 100644 --- a/vendor/go.uber.org/atomic/string.go +++ b/vendor/go.uber.org/atomic/string.go @@ -1,6 +1,6 @@ // @generated Code generated by gen-atomicwrapper. -// Copyright (c) 2020-2021 Uber Technologies, Inc. +// Copyright (c) 2020-2023 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -42,13 +42,31 @@ func NewString(val string) *String { // Load atomically loads the wrapped string. func (x *String) Load() string { - if v := x.v.Load(); v != nil { - return v.(string) - } - return _zeroString + return unpackString(x.v.Load()) } // Store atomically stores the passed string. func (x *String) Store(val string) { - x.v.Store(val) + x.v.Store(packString(val)) +} + +// CompareAndSwap is an atomic compare-and-swap for string values. +func (x *String) CompareAndSwap(old, new string) (swapped bool) { + if x.v.CompareAndSwap(packString(old), packString(new)) { + return true + } + + if old == _zeroString { + // If the old value is the empty value, then it's possible the + // underlying Value hasn't been set and is nil, so retry with nil. + return x.v.CompareAndSwap(nil, packString(new)) + } + + return false +} + +// Swap atomically stores the given string and returns the old +// value. +func (x *String) Swap(val string) (old string) { + return unpackString(x.v.Swap(packString(val))) } diff --git a/vendor/go.uber.org/atomic/string_ext.go b/vendor/go.uber.org/atomic/string_ext.go index 83d92ed..019109c 100644 --- a/vendor/go.uber.org/atomic/string_ext.go +++ b/vendor/go.uber.org/atomic/string_ext.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 Uber Technologies, Inc. +// Copyright (c) 2020-2023 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -20,9 +20,18 @@ package atomic -//go:generate bin/gen-atomicwrapper -name=String -type=string -wrapped=Value -file=string.go -// Note: No Swap as String wraps Value, which wraps the stdlib sync/atomic.Value which -// only supports Swap as of go1.17: https://github.com/golang/go/issues/39351 +//go:generate bin/gen-atomicwrapper -name=String -type=string -wrapped Value -pack packString -unpack unpackString -compareandswap -swap -file=string.go + +func packString(s string) interface{} { + return s +} + +func unpackString(v interface{}) string { + if s, ok := v.(string); ok { + return s + } + return "" +} // String returns the wrapped value. func (s *String) String() string { diff --git a/vendor/go.uber.org/atomic/time.go b/vendor/go.uber.org/atomic/time.go index 33460fc..cc2a230 100644 --- a/vendor/go.uber.org/atomic/time.go +++ b/vendor/go.uber.org/atomic/time.go @@ -1,6 +1,6 @@ // @generated Code generated by gen-atomicwrapper. -// Copyright (c) 2020-2021 Uber Technologies, Inc. +// Copyright (c) 2020-2023 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/go.uber.org/atomic/uint32.go b/vendor/go.uber.org/atomic/uint32.go index 7859a9c..4adc294 100644 --- a/vendor/go.uber.org/atomic/uint32.go +++ b/vendor/go.uber.org/atomic/uint32.go @@ -1,6 +1,6 @@ // @generated Code generated by gen-atomicint. -// Copyright (c) 2020-2021 Uber Technologies, Inc. +// Copyright (c) 2020-2023 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -66,7 +66,14 @@ func (i *Uint32) Dec() uint32 { } // CAS is an atomic compare-and-swap. +// +// Deprecated: Use CompareAndSwap. func (i *Uint32) CAS(old, new uint32) (swapped bool) { + return i.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (i *Uint32) CompareAndSwap(old, new uint32) (swapped bool) { return atomic.CompareAndSwapUint32(&i.v, old, new) } diff --git a/vendor/go.uber.org/atomic/uint64.go b/vendor/go.uber.org/atomic/uint64.go index 2f2a7db..0e2eddb 100644 --- a/vendor/go.uber.org/atomic/uint64.go +++ b/vendor/go.uber.org/atomic/uint64.go @@ -1,6 +1,6 @@ // @generated Code generated by gen-atomicint. -// Copyright (c) 2020-2021 Uber Technologies, Inc. +// Copyright (c) 2020-2023 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -66,7 +66,14 @@ func (i *Uint64) Dec() uint64 { } // CAS is an atomic compare-and-swap. +// +// Deprecated: Use CompareAndSwap. func (i *Uint64) CAS(old, new uint64) (swapped bool) { + return i.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (i *Uint64) CompareAndSwap(old, new uint64) (swapped bool) { return atomic.CompareAndSwapUint64(&i.v, old, new) } diff --git a/vendor/go.uber.org/atomic/uintptr.go b/vendor/go.uber.org/atomic/uintptr.go index ecf7a77..7d5b000 100644 --- a/vendor/go.uber.org/atomic/uintptr.go +++ b/vendor/go.uber.org/atomic/uintptr.go @@ -1,6 +1,6 @@ // @generated Code generated by gen-atomicint. -// Copyright (c) 2020-2021 Uber Technologies, Inc. +// Copyright (c) 2020-2023 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -66,7 +66,14 @@ func (i *Uintptr) Dec() uintptr { } // CAS is an atomic compare-and-swap. +// +// Deprecated: Use CompareAndSwap. func (i *Uintptr) CAS(old, new uintptr) (swapped bool) { + return i.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (i *Uintptr) CompareAndSwap(old, new uintptr) (swapped bool) { return atomic.CompareAndSwapUintptr(&i.v, old, new) } diff --git a/vendor/go.uber.org/atomic/unsafe_pointer.go b/vendor/go.uber.org/atomic/unsafe_pointer.go index 169f793..34868ba 100644 --- a/vendor/go.uber.org/atomic/unsafe_pointer.go +++ b/vendor/go.uber.org/atomic/unsafe_pointer.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Uber Technologies, Inc. +// Copyright (c) 2021-2022 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -53,6 +53,13 @@ func (p *UnsafePointer) Swap(val unsafe.Pointer) (old unsafe.Pointer) { } // CAS is an atomic compare-and-swap. +// +// Deprecated: Use CompareAndSwap func (p *UnsafePointer) CAS(old, new unsafe.Pointer) (swapped bool) { + return p.CompareAndSwap(old, new) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (p *UnsafePointer) CompareAndSwap(old, new unsafe.Pointer) (swapped bool) { return atomic.CompareAndSwapPointer(&p.v, old, new) } diff --git a/vendor/go.uber.org/atomic/value.go b/vendor/go.uber.org/atomic/value.go index 671f3a3..52caedb 100644 --- a/vendor/go.uber.org/atomic/value.go +++ b/vendor/go.uber.org/atomic/value.go @@ -25,7 +25,7 @@ import "sync/atomic" // Value shadows the type of the same name from sync/atomic // https://godoc.org/sync/atomic#Value type Value struct { - atomic.Value - _ nocmp // disallow non-atomic comparison + + atomic.Value } diff --git a/vendor/golang.org/x/sys/unix/mmap_nomremap.go b/vendor/golang.org/x/sys/unix/mmap_nomremap.go index 4b68e59..7f602ff 100644 --- a/vendor/golang.org/x/sys/unix/mmap_nomremap.go +++ b/vendor/golang.org/x/sys/unix/mmap_nomremap.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build aix || darwin || dragonfly || freebsd || openbsd || solaris +//go:build aix || darwin || dragonfly || freebsd || openbsd || solaris || zos package unix diff --git a/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go b/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go index b473038..27c41b6 100644 --- a/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go +++ b/vendor/golang.org/x/sys/unix/syscall_zos_s390x.go @@ -1520,6 +1520,14 @@ func (m *mmapper) Munmap(data []byte) (err error) { return nil } +func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) { + return mapper.Mmap(fd, offset, length, prot, flags) +} + +func Munmap(b []byte) (err error) { + return mapper.Munmap(b) +} + func Read(fd int, p []byte) (n int, err error) { n, err = read(fd, p) if raceenabled { diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index 6395a03..6525c62 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -165,6 +165,7 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys CreateFile(name *uint16, access uint32, mode uint32, sa *SecurityAttributes, createmode uint32, attrs uint32, templatefile Handle) (handle Handle, err error) [failretval==InvalidHandle] = CreateFileW //sys CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *SecurityAttributes) (handle Handle, err error) [failretval==InvalidHandle] = CreateNamedPipeW //sys ConnectNamedPipe(pipe Handle, overlapped *Overlapped) (err error) +//sys DisconnectNamedPipe(pipe Handle) (err error) //sys GetNamedPipeInfo(pipe Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) //sys GetNamedPipeHandleState(pipe Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW //sys SetNamedPipeHandleState(pipe Handle, state *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32) (err error) = SetNamedPipeHandleState @@ -348,8 +349,19 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys SetProcessPriorityBoost(process Handle, disable bool) (err error) = kernel32.SetProcessPriorityBoost //sys GetProcessWorkingSetSizeEx(hProcess Handle, lpMinimumWorkingSetSize *uintptr, lpMaximumWorkingSetSize *uintptr, flags *uint32) //sys SetProcessWorkingSetSizeEx(hProcess Handle, dwMinimumWorkingSetSize uintptr, dwMaximumWorkingSetSize uintptr, flags uint32) (err error) +//sys ClearCommBreak(handle Handle) (err error) +//sys ClearCommError(handle Handle, lpErrors *uint32, lpStat *ComStat) (err error) +//sys EscapeCommFunction(handle Handle, dwFunc uint32) (err error) +//sys GetCommState(handle Handle, lpDCB *DCB) (err error) +//sys GetCommModemStatus(handle Handle, lpModemStat *uint32) (err error) //sys GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) +//sys PurgeComm(handle Handle, dwFlags uint32) (err error) +//sys SetCommBreak(handle Handle) (err error) +//sys SetCommMask(handle Handle, dwEvtMask uint32) (err error) +//sys SetCommState(handle Handle, lpDCB *DCB) (err error) //sys SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) +//sys SetupComm(handle Handle, dwInQueue uint32, dwOutQueue uint32) (err error) +//sys WaitCommEvent(handle Handle, lpEvtMask *uint32, lpOverlapped *Overlapped) (err error) //sys GetActiveProcessorCount(groupNumber uint16) (ret uint32) //sys GetMaximumProcessorCount(groupNumber uint16) (ret uint32) //sys EnumWindows(enumFunc uintptr, param unsafe.Pointer) (err error) = user32.EnumWindows @@ -1834,3 +1846,73 @@ func ResizePseudoConsole(pconsole Handle, size Coord) error { // accept arguments that can be casted to uintptr, and Coord can't. return resizePseudoConsole(pconsole, *((*uint32)(unsafe.Pointer(&size)))) } + +// DCB constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-dcb. +const ( + CBR_110 = 110 + CBR_300 = 300 + CBR_600 = 600 + CBR_1200 = 1200 + CBR_2400 = 2400 + CBR_4800 = 4800 + CBR_9600 = 9600 + CBR_14400 = 14400 + CBR_19200 = 19200 + CBR_38400 = 38400 + CBR_57600 = 57600 + CBR_115200 = 115200 + CBR_128000 = 128000 + CBR_256000 = 256000 + + DTR_CONTROL_DISABLE = 0x00000000 + DTR_CONTROL_ENABLE = 0x00000010 + DTR_CONTROL_HANDSHAKE = 0x00000020 + + RTS_CONTROL_DISABLE = 0x00000000 + RTS_CONTROL_ENABLE = 0x00001000 + RTS_CONTROL_HANDSHAKE = 0x00002000 + RTS_CONTROL_TOGGLE = 0x00003000 + + NOPARITY = 0 + ODDPARITY = 1 + EVENPARITY = 2 + MARKPARITY = 3 + SPACEPARITY = 4 + + ONESTOPBIT = 0 + ONE5STOPBITS = 1 + TWOSTOPBITS = 2 +) + +// EscapeCommFunction constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-escapecommfunction. +const ( + SETXOFF = 1 + SETXON = 2 + SETRTS = 3 + CLRRTS = 4 + SETDTR = 5 + CLRDTR = 6 + SETBREAK = 8 + CLRBREAK = 9 +) + +// PurgeComm constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-purgecomm. +const ( + PURGE_TXABORT = 0x0001 + PURGE_RXABORT = 0x0002 + PURGE_TXCLEAR = 0x0004 + PURGE_RXCLEAR = 0x0008 +) + +// SetCommMask constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setcommmask. +const ( + EV_RXCHAR = 0x0001 + EV_RXFLAG = 0x0002 + EV_TXEMPTY = 0x0004 + EV_CTS = 0x0008 + EV_DSR = 0x0010 + EV_RLSD = 0x0020 + EV_BREAK = 0x0040 + EV_ERR = 0x0080 + EV_RING = 0x0100 +) diff --git a/vendor/golang.org/x/sys/windows/types_windows.go b/vendor/golang.org/x/sys/windows/types_windows.go index 359780f..d8cb71d 100644 --- a/vendor/golang.org/x/sys/windows/types_windows.go +++ b/vendor/golang.org/x/sys/windows/types_windows.go @@ -3380,3 +3380,27 @@ type BLOB struct { Size uint32 BlobData *byte } + +type ComStat struct { + Flags uint32 + CBInQue uint32 + CBOutQue uint32 +} + +type DCB struct { + DCBlength uint32 + BaudRate uint32 + Flags uint32 + wReserved uint16 + XonLim uint16 + XoffLim uint16 + ByteSize uint8 + Parity uint8 + StopBits uint8 + XonChar byte + XoffChar byte + ErrorChar byte + EofChar byte + EvtChar byte + wReserved1 uint16 +} diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go index e8791c8..5c6035d 100644 --- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -188,6 +188,8 @@ var ( procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject") procCancelIo = modkernel32.NewProc("CancelIo") procCancelIoEx = modkernel32.NewProc("CancelIoEx") + procClearCommBreak = modkernel32.NewProc("ClearCommBreak") + procClearCommError = modkernel32.NewProc("ClearCommError") procCloseHandle = modkernel32.NewProc("CloseHandle") procClosePseudoConsole = modkernel32.NewProc("ClosePseudoConsole") procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") @@ -212,7 +214,9 @@ var ( procDeleteProcThreadAttributeList = modkernel32.NewProc("DeleteProcThreadAttributeList") procDeleteVolumeMountPointW = modkernel32.NewProc("DeleteVolumeMountPointW") procDeviceIoControl = modkernel32.NewProc("DeviceIoControl") + procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe") procDuplicateHandle = modkernel32.NewProc("DuplicateHandle") + procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction") procExitProcess = modkernel32.NewProc("ExitProcess") procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW") procFindClose = modkernel32.NewProc("FindClose") @@ -236,6 +240,8 @@ var ( procGenerateConsoleCtrlEvent = modkernel32.NewProc("GenerateConsoleCtrlEvent") procGetACP = modkernel32.NewProc("GetACP") procGetActiveProcessorCount = modkernel32.NewProc("GetActiveProcessorCount") + procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus") + procGetCommState = modkernel32.NewProc("GetCommState") procGetCommTimeouts = modkernel32.NewProc("GetCommTimeouts") procGetCommandLineW = modkernel32.NewProc("GetCommandLineW") procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW") @@ -322,6 +328,7 @@ var ( procProcess32NextW = modkernel32.NewProc("Process32NextW") procProcessIdToSessionId = modkernel32.NewProc("ProcessIdToSessionId") procPulseEvent = modkernel32.NewProc("PulseEvent") + procPurgeComm = modkernel32.NewProc("PurgeComm") procQueryDosDeviceW = modkernel32.NewProc("QueryDosDeviceW") procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW") procQueryInformationJobObject = modkernel32.NewProc("QueryInformationJobObject") @@ -335,6 +342,9 @@ var ( procResetEvent = modkernel32.NewProc("ResetEvent") procResizePseudoConsole = modkernel32.NewProc("ResizePseudoConsole") procResumeThread = modkernel32.NewProc("ResumeThread") + procSetCommBreak = modkernel32.NewProc("SetCommBreak") + procSetCommMask = modkernel32.NewProc("SetCommMask") + procSetCommState = modkernel32.NewProc("SetCommState") procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts") procSetConsoleCursorPosition = modkernel32.NewProc("SetConsoleCursorPosition") procSetConsoleMode = modkernel32.NewProc("SetConsoleMode") @@ -342,7 +352,6 @@ var ( procSetDefaultDllDirectories = modkernel32.NewProc("SetDefaultDllDirectories") procSetDllDirectoryW = modkernel32.NewProc("SetDllDirectoryW") procSetEndOfFile = modkernel32.NewProc("SetEndOfFile") - procSetFileValidData = modkernel32.NewProc("SetFileValidData") procSetEnvironmentVariableW = modkernel32.NewProc("SetEnvironmentVariableW") procSetErrorMode = modkernel32.NewProc("SetErrorMode") procSetEvent = modkernel32.NewProc("SetEvent") @@ -351,6 +360,7 @@ var ( procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle") procSetFilePointer = modkernel32.NewProc("SetFilePointer") procSetFileTime = modkernel32.NewProc("SetFileTime") + procSetFileValidData = modkernel32.NewProc("SetFileValidData") procSetHandleInformation = modkernel32.NewProc("SetHandleInformation") procSetInformationJobObject = modkernel32.NewProc("SetInformationJobObject") procSetNamedPipeHandleState = modkernel32.NewProc("SetNamedPipeHandleState") @@ -361,6 +371,7 @@ var ( procSetStdHandle = modkernel32.NewProc("SetStdHandle") procSetVolumeLabelW = modkernel32.NewProc("SetVolumeLabelW") procSetVolumeMountPointW = modkernel32.NewProc("SetVolumeMountPointW") + procSetupComm = modkernel32.NewProc("SetupComm") procSizeofResource = modkernel32.NewProc("SizeofResource") procSleepEx = modkernel32.NewProc("SleepEx") procTerminateJobObject = modkernel32.NewProc("TerminateJobObject") @@ -379,6 +390,7 @@ var ( procVirtualQueryEx = modkernel32.NewProc("VirtualQueryEx") procVirtualUnlock = modkernel32.NewProc("VirtualUnlock") procWTSGetActiveConsoleSessionId = modkernel32.NewProc("WTSGetActiveConsoleSessionId") + procWaitCommEvent = modkernel32.NewProc("WaitCommEvent") procWaitForMultipleObjects = modkernel32.NewProc("WaitForMultipleObjects") procWaitForSingleObject = modkernel32.NewProc("WaitForSingleObject") procWriteConsoleW = modkernel32.NewProc("WriteConsoleW") @@ -1641,6 +1653,22 @@ func CancelIoEx(s Handle, o *Overlapped) (err error) { return } +func ClearCommBreak(handle Handle) (err error) { + r1, _, e1 := syscall.Syscall(procClearCommBreak.Addr(), 1, uintptr(handle), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func ClearCommError(handle Handle, lpErrors *uint32, lpStat *ComStat) (err error) { + r1, _, e1 := syscall.Syscall(procClearCommError.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(lpErrors)), uintptr(unsafe.Pointer(lpStat))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func CloseHandle(handle Handle) (err error) { r1, _, e1 := syscall.Syscall(procCloseHandle.Addr(), 1, uintptr(handle), 0, 0) if r1 == 0 { @@ -1845,6 +1873,14 @@ func DeviceIoControl(handle Handle, ioControlCode uint32, inBuffer *byte, inBuff return } +func DisconnectNamedPipe(pipe Handle) (err error) { + r1, _, e1 := syscall.Syscall(procDisconnectNamedPipe.Addr(), 1, uintptr(pipe), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetProcessHandle Handle, lpTargetHandle *Handle, dwDesiredAccess uint32, bInheritHandle bool, dwOptions uint32) (err error) { var _p0 uint32 if bInheritHandle { @@ -1857,6 +1893,14 @@ func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetP return } +func EscapeCommFunction(handle Handle, dwFunc uint32) (err error) { + r1, _, e1 := syscall.Syscall(procEscapeCommFunction.Addr(), 2, uintptr(handle), uintptr(dwFunc), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func ExitProcess(exitcode uint32) { syscall.Syscall(procExitProcess.Addr(), 1, uintptr(exitcode), 0, 0) return @@ -2058,6 +2102,22 @@ func GetActiveProcessorCount(groupNumber uint16) (ret uint32) { return } +func GetCommModemStatus(handle Handle, lpModemStat *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetCommModemStatus.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpModemStat)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func GetCommState(handle Handle, lpDCB *DCB) (err error) { + r1, _, e1 := syscall.Syscall(procGetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpDCB)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) { r1, _, e1 := syscall.Syscall(procGetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0) if r1 == 0 { @@ -2810,6 +2870,14 @@ func PulseEvent(event Handle) (err error) { return } +func PurgeComm(handle Handle, dwFlags uint32) (err error) { + r1, _, e1 := syscall.Syscall(procPurgeComm.Addr(), 2, uintptr(handle), uintptr(dwFlags), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func QueryDosDevice(deviceName *uint16, targetPath *uint16, max uint32) (n uint32, err error) { r0, _, e1 := syscall.Syscall(procQueryDosDeviceW.Addr(), 3, uintptr(unsafe.Pointer(deviceName)), uintptr(unsafe.Pointer(targetPath)), uintptr(max)) n = uint32(r0) @@ -2924,6 +2992,30 @@ func ResumeThread(thread Handle) (ret uint32, err error) { return } +func SetCommBreak(handle Handle) (err error) { + r1, _, e1 := syscall.Syscall(procSetCommBreak.Addr(), 1, uintptr(handle), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func SetCommMask(handle Handle, dwEvtMask uint32) (err error) { + r1, _, e1 := syscall.Syscall(procSetCommMask.Addr(), 2, uintptr(handle), uintptr(dwEvtMask), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func SetCommState(handle Handle, lpDCB *DCB) (err error) { + r1, _, e1 := syscall.Syscall(procSetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpDCB)), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) { r1, _, e1 := syscall.Syscall(procSetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0) if r1 == 0 { @@ -2989,14 +3081,6 @@ func SetEndOfFile(handle Handle) (err error) { return } -func SetFileValidData(handle Handle, validDataLength int64) (err error) { - r1, _, e1 := syscall.Syscall(procSetFileValidData.Addr(), 2, uintptr(handle), uintptr(validDataLength), 0) - if r1 == 0 { - err = errnoErr(e1) - } - return -} - func SetEnvironmentVariable(name *uint16, value *uint16) (err error) { r1, _, e1 := syscall.Syscall(procSetEnvironmentVariableW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), 0) if r1 == 0 { @@ -3060,6 +3144,14 @@ func SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetim return } +func SetFileValidData(handle Handle, validDataLength int64) (err error) { + r1, _, e1 := syscall.Syscall(procSetFileValidData.Addr(), 2, uintptr(handle), uintptr(validDataLength), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error) { r1, _, e1 := syscall.Syscall(procSetHandleInformation.Addr(), 3, uintptr(handle), uintptr(mask), uintptr(flags)) if r1 == 0 { @@ -3145,6 +3237,14 @@ func SetVolumeMountPoint(volumeMountPoint *uint16, volumeName *uint16) (err erro return } +func SetupComm(handle Handle, dwInQueue uint32, dwOutQueue uint32) (err error) { + r1, _, e1 := syscall.Syscall(procSetupComm.Addr(), 3, uintptr(handle), uintptr(dwInQueue), uintptr(dwOutQueue)) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func SizeofResource(module Handle, resInfo Handle) (size uint32, err error) { r0, _, e1 := syscall.Syscall(procSizeofResource.Addr(), 2, uintptr(module), uintptr(resInfo), 0) size = uint32(r0) @@ -3291,6 +3391,14 @@ func WTSGetActiveConsoleSessionId() (sessionID uint32) { return } +func WaitCommEvent(handle Handle, lpEvtMask *uint32, lpOverlapped *Overlapped) (err error) { + r1, _, e1 := syscall.Syscall(procWaitCommEvent.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(lpEvtMask)), uintptr(unsafe.Pointer(lpOverlapped))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func waitForMultipleObjects(count uint32, handles uintptr, waitAll bool, waitMilliseconds uint32) (event uint32, err error) { var _p0 uint32 if waitAll { diff --git a/vendor/modules.txt b/vendor/modules.txt index c0dee87..126cc41 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -5,7 +5,7 @@ github.com/beorn7/perks/quantile ## explicit; go 1.18 github.com/bitwarden/sdk-go github.com/bitwarden/sdk-go/internal/cinterface -# github.com/cespare/xxhash/v2 v2.2.0 +# github.com/cespare/xxhash/v2 v2.3.0 ## explicit; go 1.11 github.com/cespare/xxhash/v2 # github.com/fsnotify/fsnotify v1.7.0 @@ -15,8 +15,12 @@ github.com/fsnotify/fsnotify ## explicit; go 1.14 github.com/go-chi/chi/v5 github.com/go-chi/chi/v5/middleware -# github.com/gofrs/uuid v4.4.0+incompatible -## explicit +# github.com/go-chi/telemetry v0.3.4 +## explicit; go 1.19 +github.com/go-chi/telemetry +# github.com/golang/mock v1.6.0 +## explicit; go 1.11 +github.com/golang/mock/mockgen/model # github.com/google/uuid v1.6.0 ## explicit github.com/google/uuid @@ -59,15 +63,15 @@ github.com/pkg/errors github.com/prometheus/client_golang/prometheus github.com/prometheus/client_golang/prometheus/internal github.com/prometheus/client_golang/prometheus/promhttp -# github.com/prometheus/client_model v0.5.0 +# github.com/prometheus/client_model v0.6.1 ## explicit; go 1.19 github.com/prometheus/client_model/go -# github.com/prometheus/common v0.48.0 +# github.com/prometheus/common v0.52.3 ## explicit; go 1.20 github.com/prometheus/common/expfmt github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg github.com/prometheus/common/model -# github.com/prometheus/procfs v0.12.0 +# github.com/prometheus/procfs v0.13.0 ## explicit; go 1.19 github.com/prometheus/procfs github.com/prometheus/procfs/internal/fs @@ -113,8 +117,16 @@ github.com/spf13/viper/internal/features # github.com/subosito/gotenv v1.6.0 ## explicit; go 1.18 github.com/subosito/gotenv -# go.uber.org/atomic v1.9.0 -## explicit; go 1.13 +# github.com/twmb/murmur3 v1.1.8 +## explicit; go 1.11 +github.com/twmb/murmur3 +# github.com/uber-go/tally/v4 v4.1.16 +## explicit; go 1.15 +github.com/uber-go/tally/v4 +github.com/uber-go/tally/v4/internal/identity +github.com/uber-go/tally/v4/prometheus +# go.uber.org/atomic v1.11.0 +## explicit; go 1.18 go.uber.org/atomic # go.uber.org/multierr v1.9.0 ## explicit; go 1.19 @@ -129,7 +141,7 @@ golang.org/x/exp/slog/internal/buffer # golang.org/x/sync v0.6.0 ## explicit; go 1.18 golang.org/x/sync/singleflight -# golang.org/x/sys v0.18.0 +# golang.org/x/sys v0.19.0 ## explicit; go 1.18 golang.org/x/sys/unix golang.org/x/sys/windows From 59614041426db256a1ab1baf8ffb8d97a1cb36bf Mon Sep 17 00:00:00 2001 From: Tom Parker Date: Thu, 15 Aug 2024 18:13:08 -0400 Subject: [PATCH 02/10] Add hit counters --- internal/pkg/api/api.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index e6a46ac..8d240a8 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -16,10 +16,6 @@ import ( "github.com/pkg/errors" ) -var ( - AppMetrics = &BwsMetrics{telemetry.NewScope("app")} -) - type BwsMetrics struct { *telemetry.Scope } @@ -29,12 +25,22 @@ type API struct { WebTTL time.Duration OrgID string Client *client.Bitwarden + Metrics *BwsMetrics +} + +func (b *BwsMetrics) Counter(metric string) { + b.RecordHit(metric, nil) +} + +func (b *BwsMetrics) Gauge(metric string, value float64) { + b.RecordGauge(metric, nil, value) } func New(config *config.Config) http.Handler { api := API{ SecretTTL: config.SecretTTL, OrgID: config.OrgID, + Metrics: &BwsMetrics{telemetry.NewScope("bws-cache")}, } router := chi.NewRouter() @@ -59,9 +65,11 @@ func New(config *config.Config) http.Handler { slog.Debug("Client created") router.Route("/id", func(r chi.Router) { + api.Metrics.Counter("get_id") r.Get("/{secret_id}", api.getSecretByID) }) router.Route("/key", func(r chi.Router) { + api.Metrics.Counter("get_key") r.Get("/{secret_key}", api.getSecretByKey) }) router.Get("/reset", api.resetConnection) @@ -119,6 +127,7 @@ func (api *API) resetConnection(w http.ResponseWriter, r *http.Request) { slog.InfoContext(ctx, "Resetting cache") api.Client.Cache.Reset() + api.Metrics.Counter("cache_reset") slog.InfoContext(ctx, "Cache reset") } From 076991708ec6f594e7d00c554f79c77eaa960866 Mon Sep 17 00:00:00 2001 From: Tom Parker Date: Thu, 15 Aug 2024 18:13:31 -0400 Subject: [PATCH 03/10] Durations --- internal/pkg/api/api.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index 8d240a8..0fbcbb5 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -90,6 +90,8 @@ func (api *API) getSecretByID(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "secret_id") slog.DebugContext(ctx, fmt.Sprintf("Getting secret by ID: %s", id)) + span := api.Metrics.RecordSpan("secret_by_id", nil) + defer span.Stop() res, err := api.Client.GetByID(ctx, id, token) if err != nil { slog.ErrorContext(ctx, fmt.Sprintf("%+v", err)) @@ -112,6 +114,8 @@ func (api *API) getSecretByKey(w http.ResponseWriter, r *http.Request) { key := chi.URLParam(r, "secret_key") slog.DebugContext(ctx, fmt.Sprintf("Searching for key: %s", key)) + span := api.Metrics.RecordSpan("secret_by_key", nil) + defer span.Stop() res, err := api.Client.GetByKey(ctx, key, api.OrgID, token) if err != nil { slog.ErrorContext(ctx, fmt.Sprintf("%+v", err)) From 816879ecf103de2a0428fa3dba0e60f51c40503d Mon Sep 17 00:00:00 2001 From: Tom Parker Date: Mon, 19 Aug 2024 15:47:24 -0400 Subject: [PATCH 04/10] Add metrics --- internal/pkg/api/api.go | 17 +++-------------- internal/pkg/metrics/metrics.go | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 internal/pkg/metrics/metrics.go diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index 0fbcbb5..04acd56 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -9,6 +9,7 @@ import ( "bws-cache/internal/pkg/client" "bws-cache/internal/pkg/config" + "bws-cache/internal/pkg/metrics" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" @@ -16,31 +17,19 @@ import ( "github.com/pkg/errors" ) -type BwsMetrics struct { - *telemetry.Scope -} - type API struct { SecretTTL time.Duration WebTTL time.Duration OrgID string Client *client.Bitwarden - Metrics *BwsMetrics -} - -func (b *BwsMetrics) Counter(metric string) { - b.RecordHit(metric, nil) -} - -func (b *BwsMetrics) Gauge(metric string, value float64) { - b.RecordGauge(metric, nil, value) + Metrics *metrics.BwsMetrics } func New(config *config.Config) http.Handler { api := API{ SecretTTL: config.SecretTTL, OrgID: config.OrgID, - Metrics: &BwsMetrics{telemetry.NewScope("bws-cache")}, + Metrics: metrics.New(), } router := chi.NewRouter() diff --git a/internal/pkg/metrics/metrics.go b/internal/pkg/metrics/metrics.go new file mode 100644 index 0000000..6000b50 --- /dev/null +++ b/internal/pkg/metrics/metrics.go @@ -0,0 +1,21 @@ +package metrics + +import ( + "github.com/go-chi/telemetry" +) + +type BwsMetrics struct { + *telemetry.Scope +} + +func (b *BwsMetrics) Counter(metric string) { + b.RecordHit(metric, nil) +} + +func (b *BwsMetrics) Gauge(metric string, value float64) { + b.RecordGauge(metric, nil, value) +} + +func New() *BwsMetrics { + return &BwsMetrics{telemetry.NewScope("bws-cache")} +} From 5fb78057a04d8a36411666b50e916c0bcb7ddb99 Mon Sep 17 00:00:00 2001 From: Tom Parker Date: Mon, 19 Aug 2024 16:51:32 -0400 Subject: [PATCH 05/10] Need to move to be recorded --- internal/pkg/api/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index 04acd56..164b224 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -54,11 +54,9 @@ func New(config *config.Config) http.Handler { slog.Debug("Client created") router.Route("/id", func(r chi.Router) { - api.Metrics.Counter("get_id") r.Get("/{secret_id}", api.getSecretByID) }) router.Route("/key", func(r chi.Router) { - api.Metrics.Counter("get_key") r.Get("/{secret_key}", api.getSecretByKey) }) router.Get("/reset", api.resetConnection) @@ -67,6 +65,7 @@ func New(config *config.Config) http.Handler { } func (api *API) getSecretByID(w http.ResponseWriter, r *http.Request) { + api.Metrics.Counter("get_id") ctx := r.Context() slog.DebugContext(ctx, "Getting secret by ID") token, err := getAuthToken(r) @@ -92,6 +91,7 @@ func (api *API) getSecretByID(w http.ResponseWriter, r *http.Request) { } func (api *API) getSecretByKey(w http.ResponseWriter, r *http.Request) { + api.Metrics.Counter("get_key") ctx := r.Context() slog.DebugContext(ctx, "Getting secret by key") token, err := getAuthToken(r) From 6e397c1d0d184cebbc1d351b9632bb9e18880dfc Mon Sep 17 00:00:00 2001 From: Tom Parker Date: Mon, 19 Aug 2024 18:20:49 -0400 Subject: [PATCH 06/10] Use tags --- internal/pkg/api/api.go | 16 +++++++++++----- internal/pkg/metrics/metrics.go | 8 ++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index 164b224..9a3bfb4 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -65,7 +65,9 @@ func New(config *config.Config) http.Handler { } func (api *API) getSecretByID(w http.ResponseWriter, r *http.Request) { - api.Metrics.Counter("get_id") + tag := make(map[string]string) + tag["endpoint"] = "id" + api.Metrics.Counter("get", tag) ctx := r.Context() slog.DebugContext(ctx, "Getting secret by ID") token, err := getAuthToken(r) @@ -78,7 +80,7 @@ func (api *API) getSecretByID(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "secret_id") slog.DebugContext(ctx, fmt.Sprintf("Getting secret by ID: %s", id)) - span := api.Metrics.RecordSpan("secret_by_id", nil) + span := api.Metrics.RecordSpan("get", tag) defer span.Stop() res, err := api.Client.GetByID(ctx, id, token) if err != nil { @@ -91,7 +93,9 @@ func (api *API) getSecretByID(w http.ResponseWriter, r *http.Request) { } func (api *API) getSecretByKey(w http.ResponseWriter, r *http.Request) { - api.Metrics.Counter("get_key") + tag := make(map[string]string) + tag["endpoint"] = "key" + api.Metrics.Counter("get", tag) ctx := r.Context() slog.DebugContext(ctx, "Getting secret by key") token, err := getAuthToken(r) @@ -103,7 +107,7 @@ func (api *API) getSecretByKey(w http.ResponseWriter, r *http.Request) { key := chi.URLParam(r, "secret_key") slog.DebugContext(ctx, fmt.Sprintf("Searching for key: %s", key)) - span := api.Metrics.RecordSpan("secret_by_key", nil) + span := api.Metrics.RecordSpan("get", tag) defer span.Stop() res, err := api.Client.GetByKey(ctx, key, api.OrgID, token) if err != nil { @@ -120,7 +124,9 @@ func (api *API) resetConnection(w http.ResponseWriter, r *http.Request) { slog.InfoContext(ctx, "Resetting cache") api.Client.Cache.Reset() - api.Metrics.Counter("cache_reset") + tag := make(map[string]string) + tag["endpoint"] = "cache" + api.Metrics.Counter("get", tag) slog.InfoContext(ctx, "Cache reset") } diff --git a/internal/pkg/metrics/metrics.go b/internal/pkg/metrics/metrics.go index 6000b50..479d41b 100644 --- a/internal/pkg/metrics/metrics.go +++ b/internal/pkg/metrics/metrics.go @@ -8,12 +8,12 @@ type BwsMetrics struct { *telemetry.Scope } -func (b *BwsMetrics) Counter(metric string) { - b.RecordHit(metric, nil) +func (b *BwsMetrics) Counter(metric string, tags map[string]string) { + b.RecordHit(metric, tags) } -func (b *BwsMetrics) Gauge(metric string, value float64) { - b.RecordGauge(metric, nil, value) +func (b *BwsMetrics) Gauge(metric string, tags map[string]string, value float64) { + b.RecordGauge(metric, tags, value) } func New() *BwsMetrics { From 9ed0fc264c0573364c41760e1f3386f914124a04 Mon Sep 17 00:00:00 2001 From: Tom Parker Date: Sat, 31 Aug 2024 08:30:23 -0400 Subject: [PATCH 07/10] Use more flexible logger --- go.mod | 1 + go.sum | 2 + internal/pkg/api/api.go | 25 +- vendor/github.com/go-chi/httplog/v2/LICENSE | 20 + vendor/github.com/go-chi/httplog/v2/README.md | 96 +++++ vendor/github.com/go-chi/httplog/v2/color.go | 63 +++ .../github.com/go-chi/httplog/v2/helpers.go | 26 ++ .../github.com/go-chi/httplog/v2/httplog.go | 381 ++++++++++++++++++ .../github.com/go-chi/httplog/v2/options.go | 212 ++++++++++ .../go-chi/httplog/v2/text_handler.go | 207 ++++++++++ vendor/github.com/go-chi/httplog/v2/trace.go | 56 +++ vendor/github.com/go-chi/httplog/v2/util.go | 37 ++ vendor/modules.txt | 3 + 13 files changed, 1127 insertions(+), 2 deletions(-) create mode 100644 vendor/github.com/go-chi/httplog/v2/LICENSE create mode 100644 vendor/github.com/go-chi/httplog/v2/README.md create mode 100644 vendor/github.com/go-chi/httplog/v2/color.go create mode 100644 vendor/github.com/go-chi/httplog/v2/helpers.go create mode 100644 vendor/github.com/go-chi/httplog/v2/httplog.go create mode 100644 vendor/github.com/go-chi/httplog/v2/options.go create mode 100644 vendor/github.com/go-chi/httplog/v2/text_handler.go create mode 100644 vendor/github.com/go-chi/httplog/v2/trace.go create mode 100644 vendor/github.com/go-chi/httplog/v2/util.go diff --git a/go.mod b/go.mod index b4c3b83..0054c81 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.3 require ( github.com/bitwarden/sdk-go v0.1.1 github.com/go-chi/chi/v5 v5.1.0 + github.com/go-chi/httplog/v2 v2.1.1 github.com/go-chi/telemetry v0.3.4 github.com/google/uuid v1.6.0 github.com/jellydator/ttlcache/v3 v3.2.0 diff --git a/go.sum b/go.sum index f062f90..6428f48 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/httplog/v2 v2.1.1 h1:ojojiu4PIaoeJ/qAO4GWUxJqvYUTobeo7zmuHQJAxRk= +github.com/go-chi/httplog/v2 v2.1.1/go.mod h1:/XXdxicJsp4BA5fapgIC3VuTD+z0Z/VzukoB3VDc1YE= github.com/go-chi/telemetry v0.3.4 h1:iCe1lbqP4pOOYkxyy3Y2LjkNA1iMgp1owp0JYgdTRhM= github.com/go-chi/telemetry v0.3.4/go.mod h1:N+qwgqriyLwEPFyXAjj22GMdDlNuCaEourUPJfZBoPw= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index 9a3bfb4..579b022 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -13,6 +13,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/httplog/v2" "github.com/go-chi/telemetry" "github.com/pkg/errors" ) @@ -32,10 +33,30 @@ func New(config *config.Config) http.Handler { Metrics: metrics.New(), } + // Logger + logger := httplog.NewLogger("httplog-example", httplog.Options{ + // JSON: true, + LogLevel: slog.LevelInfo, + Concise: true, + RequestHeaders: true, + MessageFieldName: "message", + // TimeFieldFormat: time.RFC850, + Tags: map[string]string{ + "version": "v0.1.8", + "env": "prod", + }, + QuietDownRoutes: []string{ + "/", + "/metrics", + "/ping", + }, + QuietDownPeriod: 10 * time.Minute, + }) + router := chi.NewRouter() router.Use(middleware.RequestID) router.Use(middleware.RealIP) - router.Use(middleware.Logger) + router.Use(httplog.RequestLogger(logger)) router.Use(middleware.Recoverer) router.Use(middleware.Timeout(config.WebTTL)) // telemetry.Collector middleware mounts /metrics endpoint @@ -43,7 +64,7 @@ func New(config *config.Config) http.Handler { router.Use(telemetry.Collector(telemetry.Config{ AllowAny: true, }, []string{"/"})) // path prefix filters records generic http request metrics - + router.Use(middleware.Heartbeat("/ping")) // Enable profiler router.Mount("/debug", middleware.Profiler()) diff --git a/vendor/github.com/go-chi/httplog/v2/LICENSE b/vendor/github.com/go-chi/httplog/v2/LICENSE new file mode 100644 index 0000000..0bb58ba --- /dev/null +++ b/vendor/github.com/go-chi/httplog/v2/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka). + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/go-chi/httplog/v2/README.md b/vendor/github.com/go-chi/httplog/v2/README.md new file mode 100644 index 0000000..6d7a34a --- /dev/null +++ b/vendor/github.com/go-chi/httplog/v2/README.md @@ -0,0 +1,96 @@ +httplog +======= + +Small but powerful structured logging package for HTTP request logging built +on the Go 1.21+ stdlib `slog` package. + +``` +go get -u github.com/go-chi/httplog/v2 +``` + +## Example + +(see [_example/](./_example/main.go)) + +```go +package main + +import ( + "log/slog" + "net/http" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/httplog/v2" +) + +func main() { + // Logger + logger := httplog.NewLogger("httplog-example", httplog.Options{ + // JSON: true, + LogLevel: slog.LevelDebug, + Concise: true, + RequestHeaders: true, + MessageFieldName: "message", + // TimeFieldFormat: time.RFC850, + Tags: map[string]string{ + "version": "v1.0-81aa4244d9fc8076a", + "env": "dev", + }, + QuietDownRoutes: []string{ + "/", + "/ping", + }, + QuietDownPeriod: 10 * time.Second, + // SourceFieldName: "source", + }) + + // Service + r := chi.NewRouter() + r.Use(httplog.RequestLogger(logger)) + r.Use(middleware.Heartbeat("/ping")) + + r.Use(func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + httplog.LogEntrySetField(ctx, "user", slog.StringValue("user1")) + next.ServeHTTP(w, r.WithContext(ctx)) + }) + }) + + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("hello world")) + }) + + r.Get("/panic", func(w http.ResponseWriter, r *http.Request) { + panic("oh no") + }) + + r.Get("/info", func(w http.ResponseWriter, r *http.Request) { + oplog := httplog.LogEntry(r.Context()) + w.Header().Add("Content-Type", "text/plain") + oplog.Info("info here") + w.Write([]byte("info here")) + }) + + r.Get("/warn", func(w http.ResponseWriter, r *http.Request) { + oplog := httplog.LogEntry(r.Context()) + oplog.Warn("warn here") + w.WriteHeader(400) + w.Write([]byte("warn here")) + }) + + r.Get("/err", func(w http.ResponseWriter, r *http.Request) { + oplog := httplog.LogEntry(r.Context()) + oplog.Error("msg here", "err", errors.New("err here")) + w.WriteHeader(500) + w.Write([]byte("oops, err")) + }) + + http.ListenAndServe("localhost:8000", r) +} + +``` + +## License + +MIT diff --git a/vendor/github.com/go-chi/httplog/v2/color.go b/vendor/github.com/go-chi/httplog/v2/color.go new file mode 100644 index 0000000..e2192b3 --- /dev/null +++ b/vendor/github.com/go-chi/httplog/v2/color.go @@ -0,0 +1,63 @@ +package httplog + +// Ported from Goji's middleware, source: +// https://github.com/zenazn/goji/tree/master/web/middleware + +import ( + "bytes" + "fmt" + "os" +) + +var ( + // Normal colors + nBlack = []byte{'\033', '[', '3', '0', 'm'} + nRed = []byte{'\033', '[', '3', '1', 'm'} + nGreen = []byte{'\033', '[', '3', '2', 'm'} + nYellow = []byte{'\033', '[', '3', '3', 'm'} + nBlue = []byte{'\033', '[', '3', '4', 'm'} + nMagenta = []byte{'\033', '[', '3', '5', 'm'} + nCyan = []byte{'\033', '[', '3', '6', 'm'} + nWhite = []byte{'\033', '[', '3', '7', 'm'} + // Bright colors + bBlack = []byte{'\033', '[', '3', '0', ';', '1', 'm'} + bRed = []byte{'\033', '[', '3', '1', ';', '1', 'm'} + bGreen = []byte{'\033', '[', '3', '2', ';', '1', 'm'} + bYellow = []byte{'\033', '[', '3', '3', ';', '1', 'm'} + bBlue = []byte{'\033', '[', '3', '4', ';', '1', 'm'} + bMagenta = []byte{'\033', '[', '3', '5', ';', '1', 'm'} + bCyan = []byte{'\033', '[', '3', '6', ';', '1', 'm'} + bWhite = []byte{'\033', '[', '3', '7', ';', '1', 'm'} + + reset = []byte{'\033', '[', '0', 'm'} +) + +var IsTTY bool + +func init() { + // This is sort of cheating: if stdout is a character device, we assume + // that means it's a TTY. Unfortunately, there are many non-TTY + // character devices, but fortunately stdout is rarely set to any of + // them. + // + // We could solve this properly by pulling in a dependency on + // code.google.com/p/go.crypto/ssh/terminal, for instance, but as a + // heuristic for whether to print in color or in black-and-white, I'd + // really rather not. + fi, err := os.Stdout.Stat() + if err == nil { + m := os.ModeDevice | os.ModeCharDevice + IsTTY = fi.Mode()&m == m + } +} + +// colorWrite +func cW(w *bytes.Buffer, useColor bool, color []byte, s string, args ...interface{}) { + if IsTTY && useColor { + w.Write(color) + } + fmt.Fprintf(w, s, args...) + if IsTTY && useColor { + w.Write(reset) + } +} diff --git a/vendor/github.com/go-chi/httplog/v2/helpers.go b/vendor/github.com/go-chi/httplog/v2/helpers.go new file mode 100644 index 0000000..60a06ca --- /dev/null +++ b/vendor/github.com/go-chi/httplog/v2/helpers.go @@ -0,0 +1,26 @@ +package httplog + +import ( + "encoding/json" + "log/slog" + "reflect" +) + +// StructValue will convert a struct or slice of structs to a slog.Value +func StructValue(v interface{}) slog.Value { + var out interface{} + + rv := reflect.ValueOf(v) + if rv.Kind() == reflect.Slice { + // assume slice of objects + out = []map[string]interface{}{} + } else { + // assume single object + out = map[string]interface{}{} + } + + b, _ := json.Marshal(v) + json.Unmarshal(b, &out) + + return slog.AnyValue(out) +} diff --git a/vendor/github.com/go-chi/httplog/v2/httplog.go b/vendor/github.com/go-chi/httplog/v2/httplog.go new file mode 100644 index 0000000..69a68bd --- /dev/null +++ b/vendor/github.com/go-chi/httplog/v2/httplog.go @@ -0,0 +1,381 @@ +package httplog + +import ( + "context" + "fmt" + "io" + "log/slog" + "net/http" + "os" + "strings" + "sync" + "time" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" +) + +type Logger struct { + *slog.Logger + Options Options +} + +func NewLogger(serviceName string, options ...Options) *Logger { + logger := &Logger{} + if len(options) > 0 { + logger.Configure(options[0]) + } else { + logger.Configure(defaultOptions) + } + + slogger := logger.Logger.With(slog.Attr{Key: "service", Value: slog.StringValue(serviceName)}) + + if !logger.Options.Concise && len(logger.Options.Tags) > 0 { + group := []any{} + for k, v := range logger.Options.Tags { + group = append(group, slog.Attr{Key: k, Value: slog.StringValue(v)}) + } + slogger = slogger.With(slog.Group("tags", group...)) + } + + logger.Logger = slogger + return logger +} + +// RequestLogger is an http middleware to log http requests and responses. +// +// NOTE: for simplicity, RequestLogger automatically makes use of the chi RequestID and +// Recoverer middleware. +func RequestLogger(logger *Logger, skipPaths ...[]string) func(next http.Handler) http.Handler { + return chi.Chain( + middleware.RequestID, + Handler(logger, skipPaths...), + middleware.Recoverer, + ).Handler +} + +func Handler(logger *Logger, optSkipPaths ...[]string) func(next http.Handler) http.Handler { + var f middleware.LogFormatter = &requestLogger{logger.Logger, logger.Options} + + skipPaths := map[string]struct{}{} + if len(optSkipPaths) > 0 { + for _, path := range optSkipPaths[0] { + skipPaths[path] = struct{}{} + } + } + + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + // Skip the logger if the path is in the skip list + if len(skipPaths) > 0 { + _, skip := skipPaths[r.URL.Path] + if skip { + next.ServeHTTP(w, r) + return + } + } + + if rInCooldown(r, &logger.Options) { + next.ServeHTTP(w, r) + return + } + + ctx := r.Context() + if logger.Options.Trace != nil { + traceID := r.Header.Get(logger.Options.Trace.HeaderTrace) + if traceID == "" { + traceID = newID() + } + w.Header().Set(logger.Options.Trace.HeaderTrace, traceID) + ctx = context.WithValue(ctx, _contextKeyTrace, traceID) + ctx = context.WithValue(ctx, _contextKeySpan, newID()) + } + + r = r.WithContext(ctx) + + entry := f.NewLogEntry(r) + ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) + + buf := newLimitBuffer(512) + ww.Tee(buf) + + t1 := time.Now() + defer func() { + var respBody []byte + if ww.Status() >= 400 { + respBody, _ = io.ReadAll(buf) + } + entry.Write(ww.Status(), ww.BytesWritten(), ww.Header(), time.Since(t1), respBody) + }() + + next.ServeHTTP(ww, middleware.WithLogEntry(r, entry)) + } + return http.HandlerFunc(fn) + } +} + +type requestLogger struct { + Logger *slog.Logger + Options Options +} + +func (l *requestLogger) NewLogEntry(r *http.Request) middleware.LogEntry { + entry := &RequestLoggerEntry{l.Logger, l.Options, ""} + msg := fmt.Sprintf("Request: %s %s", r.Method, r.URL.Path) + + logger := l.Logger + + if traceID, ok := r.Context().Value(_contextKeyTrace).(string); ok { + logger = logger.With(slog.Attr{Key: l.Options.Trace.LogFieldTrace, Value: slog.StringValue(traceID)}) + } + if spanID, ok := r.Context().Value(_contextKeySpan).(string); ok { + logger = logger.With(slog.Attr{Key: l.Options.Trace.LogFieldSpan, Value: slog.StringValue(spanID)}) + } + + entry.Logger = logger.With(requestLogFields(r, l.Options, l.Options.RequestHeaders)) + + if !l.Options.Concise { + entry.Logger.Info(msg) + } + return entry +} + +type RequestLoggerEntry struct { + Logger *slog.Logger + Options Options + msg string +} + +func (l *RequestLoggerEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) { + msg := fmt.Sprintf("Response: %d %s", status, statusLabel(status)) + if l.msg != "" { + msg = fmt.Sprintf("%s - %s", msg, l.msg) + } + + responseLog := []any{ + slog.Attr{Key: "status", Value: slog.IntValue(status)}, + slog.Attr{Key: "bytes", Value: slog.IntValue(bytes)}, + slog.Attr{Key: "elapsed", Value: slog.Float64Value(float64(elapsed.Nanoseconds()) / 1000000.0)}, // in milliseconds + } + + if !l.Options.Concise { + // Include response header, as well for error status codes (>400) we include + // the response body so we may inspect the log message sent back to the client. + if status >= 400 { + body, _ := extra.([]byte) + responseLog = append(responseLog, slog.Attr{Key: "body", Value: slog.StringValue(string(body))}) + } + if l.Options.ResponseHeaders && len(header) > 0 { + responseLog = append(responseLog, slog.Group("header", attrsToAnys(headerLogField(header, l.Options))...)) + } + } + + l.Logger.With(slog.Group("httpResponse", responseLog...)).Log(context.Background(), statusLevel(status), msg) +} + +func (l *RequestLoggerEntry) Panic(v interface{}, stack []byte) { + stacktrace := "#" + if l.Options.JSON { + stacktrace = string(stack) + } + l.Logger = l.Logger.With( + slog.Attr{ + Key: "stacktrace", + Value: slog.StringValue(stacktrace)}, + slog.Attr{ + Key: "panic", + Value: slog.StringValue(fmt.Sprintf("%+v", v)), + }) + + l.msg = fmt.Sprintf("%+v", v) + + if !l.Options.JSON { + middleware.PrintPrettyStack(v) + } +} + +var coolDownMu sync.RWMutex +var coolDowns = map[string]time.Time{} + +func rInCooldown(r *http.Request, options *Options) bool { + routePath := r.URL.EscapedPath() + if routePath == "" { + routePath = "/" + } + if !inArray(options.QuietDownRoutes, routePath) { + return false + } + coolDownMu.RLock() + coolDownTime, ok := coolDowns[routePath] + coolDownMu.RUnlock() + if ok { + if time.Since(coolDownTime) < options.QuietDownPeriod { + return true + } + } + coolDownMu.Lock() + defer coolDownMu.Unlock() + coolDowns[routePath] = time.Now().Add(options.QuietDownPeriod) + return false +} + +func inArray(arr []string, val string) bool { + for _, v := range arr { + if v == val { + return true + } + } + return false +} + +func requestLogFields(r *http.Request, options Options, requestHeaders bool) slog.Attr { + scheme := "http" + if r.TLS != nil { + scheme = "https" + } + requestURL := fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI) + + requestFields := []any{ + slog.Attr{Key: "url", Value: slog.StringValue(requestURL)}, + slog.Attr{Key: "method", Value: slog.StringValue(r.Method)}, + slog.Attr{Key: "path", Value: slog.StringValue(r.URL.Path)}, + slog.Attr{Key: "remoteIP", Value: slog.StringValue(r.RemoteAddr)}, + slog.Attr{Key: "proto", Value: slog.StringValue(r.Proto)}, + } + if reqID := middleware.GetReqID(r.Context()); reqID != "" { + requestFields = append(requestFields, slog.Attr{Key: "requestID", Value: slog.StringValue(reqID)}) + } + + if !options.RequestHeaders { + return slog.Group("httpRequest", requestFields...) + } + + // include request headers + requestFields = append(requestFields, slog.Attr{Key: "scheme", Value: slog.StringValue(scheme)}) + if len(r.Header) > 0 { + requestFields = append(requestFields, + slog.Attr{ + Key: "header", + Value: slog.GroupValue(headerLogField(r.Header, options)...), + }) + } + + return slog.Group("httpRequest", requestFields...) +} + +func headerLogField(header http.Header, options Options) []slog.Attr { + headerField := []slog.Attr{} + for k, v := range header { + k = strings.ToLower(k) + switch { + case len(v) == 0: + continue + case len(v) == 1: + headerField = append(headerField, slog.Attr{Key: k, Value: slog.StringValue(v[0])}) + default: + headerField = append(headerField, slog.Attr{Key: k, + Value: slog.StringValue(fmt.Sprintf("[%s]", strings.Join(v, "], [")))}) + } + if k == "authorization" || k == "cookie" || k == "set-cookie" { + headerField[len(headerField)-1] = slog.Attr{ + Key: k, + Value: slog.StringValue("***"), + } + } + + for _, skip := range options.HideRequestHeaders { + if k == skip { + headerField[len(headerField)-1] = slog.Attr{ + Key: k, + Value: slog.StringValue("***"), + } + break + } + } + } + return headerField +} + +func attrsToAnys(attr []slog.Attr) []any { + attrs := make([]any, len(attr)) + for i, a := range attr { + attrs[i] = a + } + return attrs +} + +func statusLevel(status int) slog.Level { + switch { + case status <= 0: + return slog.LevelWarn + case status < 400: // for codes in 100s, 200s, 300s + return slog.LevelInfo + case status >= 400 && status < 500: + // switching to info level to be less noisy + return slog.LevelInfo + case status >= 500: + return slog.LevelError + default: + return slog.LevelInfo + } +} + +func statusLabel(status int) string { + switch { + case status >= 100 && status < 300: + return "OK" + case status >= 300 && status < 400: + return "Redirect" + case status >= 400 && status < 500: + return "Client Error" + case status >= 500: + return "Server Error" + default: + return "Unknown" + } +} + +func ErrAttr(err error) slog.Attr { + return slog.Any("err", err) +} + +// Helper methods used by the application to get the request-scoped +// logger entry and set additional fields between handlers. +// +// This is a useful pattern to use to set state on the entry as it +// passes through the handler chain, which at any point can be logged +// with a call to .Print(), .Info(), etc. + +func LogEntry(ctx context.Context) *slog.Logger { + entry, ok := ctx.Value(middleware.LogEntryCtxKey).(*RequestLoggerEntry) + if !ok || entry == nil { + handlerOpts := &slog.HandlerOptions{ + AddSource: true, + // LevelError+1 will be higher than all levels + // hence logs would be skipped + Level: slog.LevelError + 1, + // ReplaceAttr: func(attr slog.Attr) slog.Attr , + } + return slog.New(slog.NewTextHandler(os.Stdout, handlerOpts)) + } else { + return entry.Logger + } +} + +func LogEntrySetField(ctx context.Context, key string, value slog.Value) { + if entry, ok := ctx.Value(middleware.LogEntryCtxKey).(*RequestLoggerEntry); ok { + entry.Logger = entry.Logger.With(slog.Attr{Key: key, Value: value}) + } +} + +func LogEntrySetFields(ctx context.Context, fields map[string]interface{}) { + if entry, ok := ctx.Value(middleware.LogEntryCtxKey).(*RequestLoggerEntry); ok { + attrs := make([]any, len(fields)) + i := 0 + for k, v := range fields { + attrs[i] = slog.Attr{Key: k, Value: slog.AnyValue(v)} + i++ + } + entry.Logger = entry.Logger.With(attrs...) + } +} diff --git a/vendor/github.com/go-chi/httplog/v2/options.go b/vendor/github.com/go-chi/httplog/v2/options.go new file mode 100644 index 0000000..ac53495 --- /dev/null +++ b/vendor/github.com/go-chi/httplog/v2/options.go @@ -0,0 +1,212 @@ +package httplog + +import ( + "cmp" + "io" + "os" + "strings" + "time" + + "log/slog" +) + +var defaultOptions = Options{ + LogLevel: slog.LevelInfo, + LevelFieldName: "level", + JSON: false, + Concise: true, + Tags: nil, + RequestHeaders: true, + HideRequestHeaders: nil, + QuietDownRoutes: nil, + QuietDownPeriod: 0, + TimeFieldFormat: time.RFC3339Nano, + TimeFieldName: "timestamp", + MessageFieldName: "message", +} + +type Options struct { + // LogLevel defines the minimum level of severity that app should log. + // Must be one of: + // slog.LevelDebug, slog.LevelInfo, slog.LevelWarn, slog.LevelError + LogLevel slog.Level + + // LevelFieldName sets the field name for the log level or severity. + // Some providers parse and search for different field names. + LevelFieldName string + + // MessageFieldName sets the field name for the message. + // Default is "msg". + MessageFieldName string + + // JSON enables structured logging output in json. Make sure to enable this + // in production mode so log aggregators can receive data in parsable format. + // + // In local development mode, its appropriate to set this value to false to + // receive pretty output and stacktraces to stdout. + JSON bool + + // Concise mode includes fewer log details during the request flow. For example + // excluding details like request content length, user-agent and other details. + // This is useful if during development your console is too noisy. + Concise bool + + // Tags are additional fields included at the root level of all logs. + // These can be useful for example the commit hash of a build, or an environment + // name like prod/stg/dev + Tags map[string]string + + // RequestHeaders enables logging of all request headers, however sensitive + // headers like authorization, cookie and set-cookie are hidden. + RequestHeaders bool + + // HideRequestHeaders are additional requests headers which are redacted from the logs + HideRequestHeaders []string + + // ResponseHeaders enables logging of all response headers. + ResponseHeaders bool + + // QuietDownRoutes are routes which are temporarily excluded from logging for a QuietDownPeriod after it occurs + // for the first time + // to cancel noise from logging for routes that are known to be noisy. + QuietDownRoutes []string + + // QuietDownPeriod is the duration for which a route is excluded from logging after it occurs for the first time + // if the route is in QuietDownRoutes + QuietDownPeriod time.Duration + + // TimeFieldFormat defines the time format of the Time field, defaulting to "time.RFC3339Nano" see options at: + // https://pkg.go.dev/time#pkg-constants + TimeFieldFormat string + + // TimeFieldName sets the field name for the time field. + // Some providers parse and search for different field names. + TimeFieldName string + + // SourceFieldName sets the field name for the source field which logs + // the location in the program source code where the logger was called. + // If set to "" then it'll be disabled. + SourceFieldName string + + // Writer is the log writer, default is os.Stdout + Writer io.Writer + + // ReplaceAttrsOverride allows to add custom logic to replace attributes + // in addition to the default logic set in this package. + ReplaceAttrsOverride func(groups []string, a slog.Attr) slog.Attr + + // Trace is the configuration for distributed tracing. + Trace *TraceOptions +} + +// TraceOptions are the configuration options for distributed tracing. +type TraceOptions struct { + // HeaderTrace is the header key used to read the trace id from the incoming request. + // Default is "X-Trace-ID". + HeaderTrace string + // LogFieldTrace is the field name used to log the trace id. + // Default is "trace_id". + LogFieldTrace string + // LogFieldSpan is the field name used to log the span id. + // Default is "span_id". + LogFieldSpan string +} + +// Configure will set new options for the httplog instance and behaviour +// of underlying slog pkg and its global logger. +func (l *Logger) Configure(opts Options) { + // if opts.LogLevel is not set + // it would be 0 which is LevelInfo + + if opts.LevelFieldName == "" { + opts.LevelFieldName = "level" + } + + if opts.TimeFieldFormat == "" { + opts.TimeFieldFormat = time.RFC3339Nano + } + + if opts.TimeFieldName == "" { + opts.TimeFieldName = "timestamp" + } + + if len(opts.QuietDownRoutes) > 0 { + if opts.QuietDownPeriod == 0 { + opts.QuietDownPeriod = 5 * time.Minute + } + } + + // Pre-downcase all SkipHeaders + for i, header := range opts.HideRequestHeaders { + opts.HideRequestHeaders[i] = strings.ToLower(header) + } + + l.Options = opts + + var addSource bool + if opts.SourceFieldName != "" { + addSource = true + } + + replaceAttrs := func(groups []string, a slog.Attr) slog.Attr { + switch a.Key { + case slog.LevelKey: + a.Key = opts.LevelFieldName + case slog.TimeKey: + a.Key = opts.TimeFieldName + a.Value = slog.StringValue(a.Value.Time().Format(opts.TimeFieldFormat)) + case slog.MessageKey: + if opts.MessageFieldName != "" { + a.Key = opts.MessageFieldName + } + case slog.SourceKey: + if opts.SourceFieldName != "" { + a.Key = opts.SourceFieldName + } + } + + if opts.ReplaceAttrsOverride != nil { + return opts.ReplaceAttrsOverride(groups, a) + } + return a + } + + handlerOpts := &slog.HandlerOptions{ + Level: opts.LogLevel, + ReplaceAttr: replaceAttrs, + AddSource: addSource, + } + + writer := opts.Writer + if writer == nil { + writer = os.Stdout + } + + if !opts.JSON { + l.Logger = slog.New(NewPrettyHandler(writer, handlerOpts)) + } else { + l.Logger = slog.New(slog.NewJSONHandler(writer, handlerOpts)) + } + + l.Options.Trace = opts.Trace + if l.Options.Trace != nil { + l.Options.Trace.HeaderTrace = cmp.Or(l.Options.Trace.HeaderTrace, _headerTraceID) + l.Options.Trace.LogFieldTrace = cmp.Or(l.Options.Trace.LogFieldTrace, _logFieldTrace) + l.Options.Trace.LogFieldSpan = cmp.Or(l.Options.Trace.LogFieldSpan, _logFieldSpan) + } +} + +func LevelByName(name string) slog.Level { + switch strings.ToUpper(name) { + case "DEBUG": + return slog.LevelDebug + case "INFO": + return slog.LevelInfo + case "WARN": + return slog.LevelWarn + case "ERROR": + return slog.LevelError + default: + return 0 + } +} diff --git a/vendor/github.com/go-chi/httplog/v2/text_handler.go b/vendor/github.com/go-chi/httplog/v2/text_handler.go new file mode 100644 index 0000000..35e2949 --- /dev/null +++ b/vendor/github.com/go-chi/httplog/v2/text_handler.go @@ -0,0 +1,207 @@ +package httplog + +import ( + "bytes" + "context" + "io" + "log/slog" + "runtime" + "sync" + "time" +) + +type PrettyHandler struct { + opts *slog.HandlerOptions + w io.Writer + preformattedAttrs *bytes.Buffer + groupPrefix *string + groupOpen bool + mu sync.Mutex +} + +var DefaultHandlerConfig = &slog.HandlerOptions{ + Level: slog.LevelInfo, + AddSource: true, +} + +func NewPrettyHandler(w io.Writer, options ...*slog.HandlerOptions) *PrettyHandler { + var opts *slog.HandlerOptions + if len(options) == 0 { + opts = DefaultHandlerConfig + } else { + opts = options[0] + } + + return &PrettyHandler{ + opts: opts, + w: w, + preformattedAttrs: &bytes.Buffer{}, + mu: sync.Mutex{}, + } +} + +var _ slog.Handler = &PrettyHandler{} + +func (h *PrettyHandler) Enabled(ctx context.Context, level slog.Level) bool { + minLevel := slog.LevelInfo + if h.opts != nil && h.opts.Level != nil { + minLevel = h.opts.Level.Level() + } + return level >= minLevel +} + +func (h *PrettyHandler) Handle(ctx context.Context, r slog.Record) error { + buf := &bytes.Buffer{} + + if !r.Time.IsZero() { + timeAttr := slog.Attr{ + Key: slog.TimeKey, + Value: slog.TimeValue(r.Time), + } + if h.opts != nil && h.opts.ReplaceAttr != nil { + timeAttr = h.opts.ReplaceAttr([]string{}, timeAttr) + } else { + timeAttr.Value = slog.StringValue(timeAttr.Value.Time().Format(time.RFC3339Nano)) + } + // write time, level and source to buf + cW(buf, false, bBlack, "%s", timeAttr.Value.String()) + buf.WriteString(" ") + } + + levelAttr := slog.Attr{ + Key: slog.LevelKey, + Value: slog.StringValue(r.Level.String()), + } + if h.opts != nil && h.opts.ReplaceAttr != nil { + levelAttr = h.opts.ReplaceAttr([]string{}, levelAttr) + } + cW(buf, true, levelColor(r.Level), "%s", levelAttr.Value.String()) + buf.WriteString(" ") + + if h.opts != nil && h.opts.AddSource { + s := source(r) + file := s.File + line := s.Line + function := s.Function + cW(buf, true, nGreen, "%s:%s:%d", function, file, line) + buf.WriteString(" ") + } + + // write message to buf + cW(buf, true, bWhite, "%s", r.Message) + buf.WriteString(" ") + // write preformatted attrs to buf + buf.Write(h.preformattedAttrs.Bytes()) + // close group in preformatted attrs if open\ + if h.groupOpen { + cW(h.preformattedAttrs, true, nWhite, "%s", "}") + } + + // write record level attrs to buf + attrs := []slog.Attr{} + r.Attrs(func(attr slog.Attr) bool { + attrs = append(attrs, attr) + return true + }) + writeAttrs(buf, attrs, false) + + buf.WriteString("\n") + h.mu.Lock() + defer h.mu.Unlock() + h.w.Write(buf.Bytes()) + return nil +} + +func (h *PrettyHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + h2 := h.clone() + writeAttrs(h2.preformattedAttrs, attrs, false) + return h2 +} + +func source(r slog.Record) *slog.Source { + fs := runtime.CallersFrames([]uintptr{r.PC}) + f, _ := fs.Next() + return &slog.Source{ + Function: f.Function, + File: f.File, + Line: f.Line, + } +} + +func writeAttrs(w *bytes.Buffer, attrs []slog.Attr, insideGroup bool) { + for i, attr := range attrs { + cW(w, true, nYellow, "%s: ", attr.Key) + if insideGroup && i == len(attrs)-1 { + writeAttrValue(w, attr.Value, false) + } else { + writeAttrValue(w, attr.Value, true) + } + } +} + +func writeAttrValue(w *bytes.Buffer, value slog.Value, appendSpace bool) { + if appendSpace { + defer w.WriteString(" ") + } + switch v := value.Kind(); v { + case slog.KindString: + cW(w, true, nCyan, "%q", value.String()) + case slog.KindBool: + cW(w, true, nCyan, "%t", value.Bool()) + case slog.KindInt64: + cW(w, true, nCyan, "%d", value.Int64()) + case slog.KindDuration: + cW(w, true, nCyan, "%s", value.Duration().String()) + case slog.KindFloat64: + cW(w, true, nCyan, "%f", value.Float64()) + case slog.KindTime: + cW(w, true, nCyan, "%s", value.Time().Format(time.RFC3339)) + case slog.KindUint64: + cW(w, true, nCyan, "%d", value.Uint64()) + case slog.KindGroup: + cW(w, true, nWhite, "{") + writeAttrs(w, value.Group(), true) + cW(w, true, nWhite, "%s", "}") + default: + cW(w, true, nCyan, "%s", value.String()) + } +} + +func levelColor(l slog.Level) []byte { + switch l { + case slog.LevelDebug: + return nYellow + case slog.LevelInfo: + return nGreen + case slog.LevelWarn: + return nRed + case slog.LevelError: + return bRed + default: + return bWhite + } +} + +func (h *PrettyHandler) WithGroup(name string) slog.Handler { + h2 := h.clone() + if h2.groupPrefix != nil { + // end old group + cW(h2.preformattedAttrs, true, nWhite, "}") + } + h2.groupOpen = true + h2.groupPrefix = &name + cW(h2.preformattedAttrs, true, bMagenta, "%s: {", name) + return h +} + +func (h *PrettyHandler) clone() *PrettyHandler { + newBuffer := &bytes.Buffer{} + newBuffer.Write(h.preformattedAttrs.Bytes()) + + return &PrettyHandler{ + opts: h.opts, + w: h.w, + groupPrefix: h.groupPrefix, + preformattedAttrs: newBuffer, + } +} diff --git a/vendor/github.com/go-chi/httplog/v2/trace.go b/vendor/github.com/go-chi/httplog/v2/trace.go new file mode 100644 index 0000000..393ae3b --- /dev/null +++ b/vendor/github.com/go-chi/httplog/v2/trace.go @@ -0,0 +1,56 @@ +package httplog + +import ( + "cmp" + "crypto/rand" + "encoding/hex" + "net/http" +) + +const ( + _headerTraceID = "X-Trace-ID" + _logFieldTrace = "trace_id" + _logFieldSpan = "span_id" +) + +type contextKey struct { + name string +} + +func (k *contextKey) String() string { + return "httplog context value " + k.name +} + +var ( + _contextKeyTrace = &contextKey{"trace_id"} + _contextKeySpan = &contextKey{"span_id"} +) + +// NeTransport returns a new http.RoundTripper that propagates the TraceID. +func NewTransport(header string, base http.RoundTripper) http.RoundTripper { + if base == nil { + base = http.DefaultTransport + } + return traceTransport{ + Header: cmp.Or(header, _headerTraceID), + Base: base, + } +} + +type traceTransport struct { + Header string + Base http.RoundTripper +} + +func (t traceTransport) RoundTrip(r *http.Request) (*http.Response, error) { + if id, ok := r.Context().Value(_contextKeyTrace).(string); ok { + r.Header.Set(cmp.Or(t.Header, _headerTraceID), id) + } + return t.Base.RoundTrip(r) +} + +func newID() string { + b := make([]byte, 16) + rand.Read(b) + return hex.EncodeToString(b) +} diff --git a/vendor/github.com/go-chi/httplog/v2/util.go b/vendor/github.com/go-chi/httplog/v2/util.go new file mode 100644 index 0000000..dcb7851 --- /dev/null +++ b/vendor/github.com/go-chi/httplog/v2/util.go @@ -0,0 +1,37 @@ +package httplog + +import ( + "bytes" + "io" +) + +// limitBuffer is used to pipe response body information from the +// response writer to a certain limit amount. The idea is to read +// a portion of the response body such as an error response so we +// may log it. +type limitBuffer struct { + *bytes.Buffer + limit int +} + +func newLimitBuffer(size int) io.ReadWriter { + return limitBuffer{ + Buffer: bytes.NewBuffer(make([]byte, 0, size)), + limit: size, + } +} + +func (b limitBuffer) Write(p []byte) (n int, err error) { + if b.Buffer.Len() >= b.limit { + return len(p), nil + } + limit := b.limit + if len(p) < limit { + limit = len(p) + } + return b.Buffer.Write(p[:limit]) +} + +func (b limitBuffer) Read(p []byte) (n int, err error) { + return b.Buffer.Read(p) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 126cc41..277bba4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -15,6 +15,9 @@ github.com/fsnotify/fsnotify ## explicit; go 1.14 github.com/go-chi/chi/v5 github.com/go-chi/chi/v5/middleware +# github.com/go-chi/httplog/v2 v2.1.1 +## explicit; go 1.21 +github.com/go-chi/httplog/v2 # github.com/go-chi/telemetry v0.3.4 ## explicit; go 1.19 github.com/go-chi/telemetry From 569878b8ef2877501159449b61bec7b78154f9a5 Mon Sep 17 00:00:00 2001 From: Tom Parker Date: Sat, 31 Aug 2024 08:32:09 -0400 Subject: [PATCH 08/10] Update example data --- internal/pkg/api/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index 579b022..9651461 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -34,13 +34,13 @@ func New(config *config.Config) http.Handler { } // Logger - logger := httplog.NewLogger("httplog-example", httplog.Options{ + logger := httplog.NewLogger("bws-cache", httplog.Options{ // JSON: true, LogLevel: slog.LevelInfo, Concise: true, RequestHeaders: true, MessageFieldName: "message", - // TimeFieldFormat: time.RFC850, + TimeFieldFormat: time.RFC850, Tags: map[string]string{ "version": "v0.1.8", "env": "prod", From 15d7096aaabbeb31f8e8b0aa563298f7db412262 Mon Sep 17 00:00:00 2001 From: Tom Parker Date: Sun, 1 Sep 2024 09:20:47 -0400 Subject: [PATCH 09/10] Clean up log options --- internal/pkg/api/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index 9651461..45d72d4 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -35,12 +35,12 @@ func New(config *config.Config) http.Handler { // Logger logger := httplog.NewLogger("bws-cache", httplog.Options{ - // JSON: true, + JSON: true, LogLevel: slog.LevelInfo, Concise: true, RequestHeaders: true, MessageFieldName: "message", - TimeFieldFormat: time.RFC850, + TimeFieldFormat: time.RFC3339, Tags: map[string]string{ "version": "v0.1.8", "env": "prod", From 9a92980103fe66fc069665d94b952845d2239cce Mon Sep 17 00:00:00 2001 From: Tom Parker Date: Sun, 1 Sep 2024 09:29:09 -0400 Subject: [PATCH 10/10] Make version dynamic --- internal/pkg/api/api.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index 45d72d4..94c125b 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -4,6 +4,7 @@ import ( "fmt" "log/slog" "net/http" + "runtime/debug" "strings" "time" @@ -18,6 +19,18 @@ import ( "github.com/pkg/errors" ) +var commit = func() string { + if info, ok := debug.ReadBuildInfo(); ok { + for _, setting := range info.Settings { + if setting.Key == "vcs.revision" { + return setting.Value + } + } + } + + return "" +}() + type API struct { SecretTTL time.Duration WebTTL time.Duration @@ -42,8 +55,7 @@ func New(config *config.Config) http.Handler { MessageFieldName: "message", TimeFieldFormat: time.RFC3339, Tags: map[string]string{ - "version": "v0.1.8", - "env": "prod", + "version": commit, }, QuietDownRoutes: []string{ "/",