pax_global_header00006660000000000000000000000064145714065460014526gustar00rootroot0000000000000052 comment=61eed72d6bdef7c5f51be1c7ae7c824750f53efe ratelimit-0.3.1/000077500000000000000000000000001457140654600135215ustar00rootroot00000000000000ratelimit-0.3.1/.github/000077500000000000000000000000001457140654600150615ustar00rootroot00000000000000ratelimit-0.3.1/.github/workflows/000077500000000000000000000000001457140654600171165ustar00rootroot00000000000000ratelimit-0.3.1/.github/workflows/benchmark.yaml000066400000000000000000000006201457140654600217320ustar00rootroot00000000000000name: benchmark on: push: branches: - main permissions: contents: read jobs: test: name: Benchmark runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 with: go-version-file: 'go.mod' check-latest: true cache-dependency-path: '**/go.sum' - name: Benchmark run: make bench ratelimit-0.3.1/.github/workflows/cover.yaml000066400000000000000000000010571457140654600211230ustar00rootroot00000000000000name: cover on: [push] permissions: contents: read jobs: test: name: Upload coverage runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Go uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} cache-dependency-path: '**/go.sum' - name: Download dependencies run: go mod download - name: Make coverage run: make cover - name: Upload coverage to codecov.io uses: codecov/codecov-action@v3 ratelimit-0.3.1/.github/workflows/test.yaml000066400000000000000000000017401457140654600207630ustar00rootroot00000000000000name: test on: pull_request: branches: ['*'] permissions: contents: read jobs: test: name: Run tests runs-on: ubuntu-latest strategy: matrix: go: ["1.19.x", "1.20.x"] include: - go: 1.20.x latest: true steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Go uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} cache-dependency-path: '**/go.sum' - name: Download dependencies run: go mod download - name: Lint if: matrix.latest run: make lint - name: Test run: make test - name: Make coverage if: matrix.latest run: make cover - name: Upload coverage to codecov.io if: matrix.latest uses: codecov/codecov-action@v3 # TODO decide whether we want to benchmark on every run. # name: Benchmark # run: make bench ratelimit-0.3.1/.gitignore000066400000000000000000000001271457140654600155110ustar00rootroot00000000000000/bin /vendor cover.html cover.out profile.out stat.csv stat.txt stat.html *.swp .idea ratelimit-0.3.1/CHANGELOG.md000066400000000000000000000022301457140654600153270ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## Unreleased - No changes yet. ## v0.3.1 - 2024-03-04 ### Fixed - Fixed a bug related to maxSlack boundary detection. #124 Thanks to @smallnest for reporting and @storozhukBM for fixing. ## v0.3.0 - 2023-07-08 ### Changed - Switched to a more efficient internal implementation. No API or behavior changes. [#100](https://github.com/uber-go/ratelimit/pull/100) ## v0.2.0 - 2021-03-02 ### Added - Allow configuring the limiter with custom slack. [#64](https://github.com/uber-go/ratelimit/pull/64) - Allow configuring the limiter per arbitrary time duration. [#54](https://github.com/uber-go/ratelimit/pull/54) ### Changed - Switched from Glide to Go Modules. ### Fixed - Fix not working slack. [#60](https://github.com/uber-go/ratelimit/pull/60) ## v0.1.0 ### Fixed - Changed the import path for `go.uber.org/atomic` to its newer, canonical import path. [#18](https://github.com/uber-go/ratelimit/issues/18) ratelimit-0.3.1/LICENSE000066400000000000000000000021011457140654600145200ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 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.ratelimit-0.3.1/Makefile000066400000000000000000000026351457140654600151670ustar00rootroot00000000000000# Directory to put `go install`ed binaries in. export GOBIN ?= $(shell pwd)/bin GO_FILES := $(shell \ find . '(' -path '*/.*' -o -path './vendor' ')' -prune \ -o -name '*.go' -print | cut -b3-) .PHONY: bench bench: bin/benchstat bin/benchart go test -timeout 3h -count=5 -run=xxx -bench=BenchmarkRateLimiter ./... | tee stat.txt @$(GOBIN)/benchstat stat.txt @$(GOBIN)/benchstat -csv stat.txt > stat.csv @$(GOBIN)/benchart 'RateLimiter;xAxisType=log' stat.csv stat.html @open stat.html bin/benchstat: tools/go.mod @cd tools && go install golang.org/x/perf/cmd/benchstat bin/benchart: tools/go.mod @cd tools && go install github.com/storozhukBM/benchart bin/golint: tools/go.mod @cd tools && go install golang.org/x/lint/golint bin/staticcheck: tools/go.mod @cd tools && go install honnef.co/go/tools/cmd/staticcheck .PHONY: build build: go build ./... .PHONY: cover cover: go test -coverprofile=cover.out -coverpkg=./... -v ./... go tool cover -html=cover.out -o cover.html .PHONY: gofmt gofmt: $(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX)) @gofmt -e -s -l $(GO_FILES) > $(FMT_LOG) || true @[ ! -s "$(FMT_LOG)" ] || (echo "gofmt failed:" | cat - $(FMT_LOG) && false) .PHONY: golint golint: bin/golint @$(GOBIN)/golint -set_exit_status ./... .PHONY: lint lint: gofmt golint staticcheck .PHONY: staticcheck staticcheck: bin/staticcheck @$(GOBIN)/staticcheck ./... .PHONY: test test: go test -race ./... ratelimit-0.3.1/README.md000066400000000000000000000037221457140654600150040ustar00rootroot00000000000000# Go rate limiter [![GoDoc][doc-img]][doc] [![Coverage Status][cov-img]][cov] ![test][test-img] This package provides a Golang implementation of the leaky-bucket rate limit algorithm. This implementation refills the bucket based on the time elapsed between requests instead of requiring an interval clock to fill the bucket discretely. Create a rate limiter with a maximum number of operations to perform per second. Call Take() before each operation. Take will sleep until you can continue. ```go import ( "fmt" "time" "go.uber.org/ratelimit" ) func main() { rl := ratelimit.New(100) // per second prev := time.Now() for i := 0; i < 10; i++ { now := rl.Take() fmt.Println(i, now.Sub(prev)) prev = now } // Output: // 0 0 // 1 10ms // 2 10ms // 3 10ms // 4 10ms // 5 10ms // 6 10ms // 7 10ms // 8 10ms // 9 10ms } ``` ## FAQ: - What's the major diff v.s. https://pkg.go.dev/golang.org/x/time/rate? (based on #77) This ratelimiter was meant to have a (1) simple API and (2) minimal overhead. For more complex use-cases [x/time/rate] is a great choice. See [here][redit] for historical context, and [here][bench] for benchmarks (from 2016). - Why does example_test.go fail when I run it locally on Windows? (based on #80) Windows has some known issues with timers precision. See golang/go#44343. We don't expect to work around it. [cov-img]: https://codecov.io/gh/uber-go/ratelimit/branch/master/graph/badge.svg?token=zhLeUjjrm2 [cov]: https://codecov.io/gh/uber-go/ratelimit [doc-img]: https://pkg.go.dev/badge/go.uber.org/ratelimit [doc]: https://pkg.go.dev/go.uber.org/ratelimit [test-img]: https://github.com/uber-go/ratelimit/workflows/test/badge.svg [redit]: https://www.reddit.com/r/golang/comments/59k2bi/ubergoratelimit_a_golang_blocking_leakybucket/d99ob9q [x/time/rate]: https://pkg.go.dev/golang.org/x/time/rate [bench]: https://gist.github.com/prashantv/26016a7dbc6fc1ec52d8c2b6591f3582 ratelimit-0.3.1/example_test.go000066400000000000000000000015061457140654600165440ustar00rootroot00000000000000package ratelimit_test import ( "fmt" "time" "go.uber.org/ratelimit" ) func Example_default() { rl := ratelimit.New(100) // per second, some slack. rl.Take() // Initialize. time.Sleep(time.Millisecond * 45) // Let some time pass. prev := time.Now() for i := 0; i < 10; i++ { now := rl.Take() if i > 0 { fmt.Println(i, now.Sub(prev).Round(time.Millisecond*2)) } prev = now } // Output: // 1 0s // 2 0s // 3 0s // 4 4ms // 5 10ms // 6 10ms // 7 10ms // 8 10ms // 9 10ms } func Example_withoutSlack() { rl := ratelimit.New(100, ratelimit.WithoutSlack) // per second, no slack. prev := time.Now() for i := 0; i < 6; i++ { now := rl.Take() if i > 0 { fmt.Println(i, now.Sub(prev)) } prev = now } // Output: // 1 10ms // 2 10ms // 3 10ms // 4 10ms // 5 10ms } ratelimit-0.3.1/go.mod000066400000000000000000000005061457140654600146300ustar00rootroot00000000000000module go.uber.org/ratelimit go 1.20 require ( github.com/benbjohnson/clock v1.3.0 github.com/stretchr/testify v1.6.1 go.uber.org/atomic v1.7.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) ratelimit-0.3.1/go.sum000066400000000000000000000027751457140654600146670ustar00rootroot00000000000000github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ratelimit-0.3.1/limiter_atomic.go000066400000000000000000000072371457140654600170620ustar00rootroot00000000000000// Copyright (c) 2016,2020 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 ratelimit // import "go.uber.org/ratelimit" import ( "time" "sync/atomic" "unsafe" ) type state struct { last time.Time sleepFor time.Duration } type atomicLimiter struct { state unsafe.Pointer //lint:ignore U1000 Padding is unused but it is crucial to maintain performance // of this rate limiter in case of collocation with other frequently accessed memory. padding [56]byte // cache line size - state pointer size = 64 - 8; created to avoid false sharing. perRequest time.Duration maxSlack time.Duration clock Clock } // newAtomicBased returns a new atomic based limiter. func newAtomicBased(rate int, opts ...Option) *atomicLimiter { // TODO consider moving config building to the implementation // independent code. config := buildConfig(opts) perRequest := config.per / time.Duration(rate) l := &atomicLimiter{ perRequest: perRequest, maxSlack: -1 * time.Duration(config.slack) * perRequest, clock: config.clock, } initialState := state{ last: time.Time{}, sleepFor: 0, } atomic.StorePointer(&l.state, unsafe.Pointer(&initialState)) return l } // Take blocks to ensure that the time spent between multiple // Take calls is on average per/rate. func (t *atomicLimiter) Take() time.Time { var ( newState state taken bool interval time.Duration ) for !taken { now := t.clock.Now() previousStatePointer := atomic.LoadPointer(&t.state) oldState := (*state)(previousStatePointer) newState = state{ last: now, sleepFor: oldState.sleepFor, } // If this is our first request, then we allow it. if oldState.last.IsZero() { taken = atomic.CompareAndSwapPointer(&t.state, previousStatePointer, unsafe.Pointer(&newState)) continue } // sleepFor calculates how much time we should sleep based on // the perRequest budget and how long the last request took. // Since the request may take longer than the budget, this number // can get negative, and is summed across requests. newState.sleepFor += t.perRequest - now.Sub(oldState.last) // We shouldn't allow sleepFor to get too negative, since it would mean that // a service that slowed down a lot for a short period of time would get // a much higher RPS following that. if newState.sleepFor < t.maxSlack { newState.sleepFor = t.maxSlack } if newState.sleepFor > 0 { newState.last = newState.last.Add(newState.sleepFor) interval, newState.sleepFor = newState.sleepFor, 0 } taken = atomic.CompareAndSwapPointer(&t.state, previousStatePointer, unsafe.Pointer(&newState)) } t.clock.Sleep(interval) return newState.last } ratelimit-0.3.1/limiter_atomic_int64.go000066400000000000000000000070641457140654600201040ustar00rootroot00000000000000// 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. package ratelimit // import "go.uber.org/ratelimit" import ( "sync/atomic" "time" ) type atomicInt64Limiter struct { //lint:ignore U1000 Padding is unused but it is crucial to maintain performance // of this rate limiter in case of collocation with other frequently accessed memory. prepadding [64]byte // cache line size = 64; created to avoid false sharing. state int64 // unix nanoseconds of the next permissions issue. //lint:ignore U1000 like prepadding. postpadding [56]byte // cache line size - state size = 64 - 8; created to avoid false sharing. perRequest time.Duration maxSlack time.Duration clock Clock } // newAtomicBased returns a new atomic based limiter. func newAtomicInt64Based(rate int, opts ...Option) *atomicInt64Limiter { // TODO consider moving config building to the implementation // independent code. config := buildConfig(opts) perRequest := config.per / time.Duration(rate) l := &atomicInt64Limiter{ perRequest: perRequest, maxSlack: time.Duration(config.slack) * perRequest, clock: config.clock, } atomic.StoreInt64(&l.state, 0) return l } // Take blocks to ensure that the time spent between multiple // Take calls is on average time.Second/rate. func (t *atomicInt64Limiter) Take() time.Time { var ( newTimeOfNextPermissionIssue int64 now int64 ) for { now = t.clock.Now().UnixNano() timeOfNextPermissionIssue := atomic.LoadInt64(&t.state) switch { case timeOfNextPermissionIssue == 0 || (t.maxSlack == 0 && now-timeOfNextPermissionIssue > int64(t.perRequest)): // if this is our first call or t.maxSlack == 0 we need to shrink issue time to now newTimeOfNextPermissionIssue = now case t.maxSlack > 0 && now-timeOfNextPermissionIssue > int64(t.maxSlack)+int64(t.perRequest): // a lot of nanoseconds passed since the last Take call // we will limit max accumulated time to maxSlack newTimeOfNextPermissionIssue = now - int64(t.maxSlack) default: // calculate the time at which our permission was issued newTimeOfNextPermissionIssue = timeOfNextPermissionIssue + int64(t.perRequest) } if atomic.CompareAndSwapInt64(&t.state, timeOfNextPermissionIssue, newTimeOfNextPermissionIssue) { break } } sleepDuration := time.Duration(newTimeOfNextPermissionIssue - now) if sleepDuration > 0 { t.clock.Sleep(sleepDuration) return time.Unix(0, newTimeOfNextPermissionIssue) } // return now if we don't sleep as atomicLimiter does return time.Unix(0, now) } ratelimit-0.3.1/limiter_mutexbased.go000066400000000000000000000054261457140654600177450ustar00rootroot00000000000000// Copyright (c) 2016,2020 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 ratelimit // import "go.uber.org/ratelimit" import ( "sync" "time" ) type mutexLimiter struct { sync.Mutex last time.Time sleepFor time.Duration perRequest time.Duration maxSlack time.Duration clock Clock } // newMutexBased returns a new mutex based limiter. func newMutexBased(rate int, opts ...Option) *mutexLimiter { // TODO consider moving config building to the implementation // independent code. config := buildConfig(opts) perRequest := config.per / time.Duration(rate) l := &mutexLimiter{ perRequest: perRequest, maxSlack: -1 * time.Duration(config.slack) * perRequest, clock: config.clock, } return l } // Take blocks to ensure that the time spent between multiple // Take calls is on average per/rate. func (t *mutexLimiter) Take() time.Time { t.Lock() defer t.Unlock() now := t.clock.Now() // If this is our first request, then we allow it. if t.last.IsZero() { t.last = now return t.last } // sleepFor calculates how much time we should sleep based on // the perRequest budget and how long the last request took. // Since the request may take longer than the budget, this number // can get negative, and is summed across requests. t.sleepFor += t.perRequest - now.Sub(t.last) // We shouldn't allow sleepFor to get too negative, since it would mean that // a service that slowed down a lot for a short period of time would get // a much higher RPS following that. if t.sleepFor < t.maxSlack { t.sleepFor = t.maxSlack } // If sleepFor is positive, then we should sleep now. if t.sleepFor > 0 { t.clock.Sleep(t.sleepFor) t.last = now.Add(t.sleepFor) t.sleepFor = 0 } else { t.last = now } return t.last } ratelimit-0.3.1/ratelimit.go000066400000000000000000000073101457140654600160430ustar00rootroot00000000000000// Copyright (c) 2016,2020 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 ratelimit // import "go.uber.org/ratelimit" import ( "time" "github.com/benbjohnson/clock" ) // Note: This file is inspired by: // https://github.com/prashantv/go-bench/blob/master/ratelimit // Limiter is used to rate-limit some process, possibly across goroutines. // The process is expected to call Take() before every iteration, which // may block to throttle the goroutine. type Limiter interface { // Take should block to make sure that the RPS is met. Take() time.Time } // Clock is the minimum necessary interface to instantiate a rate limiter with // a clock or mock clock, compatible with clocks created using // github.com/andres-erbsen/clock. type Clock interface { Now() time.Time Sleep(time.Duration) } // config configures a limiter. type config struct { clock Clock slack int per time.Duration } // New returns a Limiter that will limit to the given RPS. func New(rate int, opts ...Option) Limiter { return newAtomicInt64Based(rate, opts...) } // buildConfig combines defaults with options. func buildConfig(opts []Option) config { c := config{ clock: clock.New(), slack: 10, per: time.Second, } for _, opt := range opts { opt.apply(&c) } return c } // Option configures a Limiter. type Option interface { apply(*config) } type clockOption struct { clock Clock } func (o clockOption) apply(c *config) { c.clock = o.clock } // WithClock returns an option for ratelimit.New that provides an alternate // Clock implementation, typically a mock Clock for testing. func WithClock(clock Clock) Option { return clockOption{clock: clock} } type slackOption int func (o slackOption) apply(c *config) { c.slack = int(o) } // WithoutSlack configures the limiter to be strict and not to accumulate // previously "unspent" requests for future bursts of traffic. var WithoutSlack Option = slackOption(0) // WithSlack configures custom slack. // Slack allows the limiter to accumulate "unspent" requests // for future bursts of traffic. func WithSlack(slack int) Option { return slackOption(slack) } type perOption time.Duration func (p perOption) apply(c *config) { c.per = time.Duration(p) } // Per allows configuring limits for different time windows. // // The default window is one second, so New(100) produces a one hundred per // second (100 Hz) rate limiter. // // New(2, Per(60*time.Second)) creates a 2 per minute rate limiter. func Per(per time.Duration) Option { return perOption(per) } type unlimited struct{} // NewUnlimited returns a RateLimiter that is not limited. func NewUnlimited() Limiter { return unlimited{} } func (unlimited) Take() time.Time { return time.Now() } ratelimit-0.3.1/ratelimit_bench_test.go000066400000000000000000000040311457140654600202360ustar00rootroot00000000000000package ratelimit import ( "fmt" "runtime" "sync" "testing" "go.uber.org/atomic" ) func BenchmarkRateLimiter(b *testing.B) { count := atomic.NewInt64(0) for _, procs := range []int{1, 4, 8, 16} { runtime.GOMAXPROCS(procs) for name, limiter := range map[string]Limiter{ "atomic": newAtomicBased(b.N * 1000000000000), "atomic_int64": newAtomicInt64Based(b.N * 1000000000000), "mutex": newMutexBased(b.N * 1000000000000), } { for ng := 1; ng < 16; ng++ { runner(b, name, procs, ng, limiter, count) } for ng := 16; ng < 128; ng += 8 { runner(b, name, procs, ng, limiter, count) } for ng := 128; ng < 512; ng += 16 { runner(b, name, procs, ng, limiter, count) } for ng := 512; ng < 1024; ng += 32 { runner(b, name, procs, ng, limiter, count) } for ng := 1024; ng < 2048; ng += 64 { runner(b, name, procs, ng, limiter, count) } for ng := 2048; ng < 4096; ng += 128 { runner(b, name, procs, ng, limiter, count) } for ng := 4096; ng < 16384; ng += 512 { runner(b, name, procs, ng, limiter, count) } for ng := 16384; ng < 65536; ng += 2048 { runner(b, name, procs, ng, limiter, count) } } } fmt.Printf("\nmark%d\n", count.Load()) } func runner(b *testing.B, name string, procs int, ng int, limiter Limiter, count *atomic.Int64) bool { return b.Run(fmt.Sprintf("type:%s;max_procs:%d;goroutines:%d", name, procs, ng), func(b *testing.B) { b.ReportAllocs() var wg sync.WaitGroup trigger := atomic.NewBool(true) n := b.N batchSize := n / ng if batchSize == 0 { batchSize = n } for n > 0 { wg.Add(1) batch := min(n, batchSize) n -= batch go func(quota int) { for trigger.Load() { runtime.Gosched() } localCnt := 0 for i := 0; i < quota; i++ { res := limiter.Take() localCnt += res.Nanosecond() } count.Add(int64(localCnt)) wg.Done() }(batch) } b.StartTimer() trigger.Store(false) wg.Wait() b.StopTimer() }) } func min(a, b int) int { if a < b { return a } return b } ratelimit-0.3.1/ratelimit_test.go000066400000000000000000000216471457140654600171130ustar00rootroot00000000000000package ratelimit import ( "sync" "testing" "time" "go.uber.org/atomic" "github.com/benbjohnson/clock" "github.com/stretchr/testify/assert" ) type testRunner interface { // createLimiter builds a limiter with given options. createLimiter(int, ...Option) Limiter // takeOnceAfter attempts to Take at a specific time. takeOnceAfter(time.Duration, Limiter) // startTaking tries to Take() on passed in limiters in a loop/goroutine. startTaking(rls ...Limiter) // assertCountAt asserts the limiters have Taken() a number of times at the given time. // It's a thin wrapper around afterFunc to reduce boilerplate code. assertCountAt(d time.Duration, count int) // afterFunc executes a func at a given time. // not using clock.AfterFunc because andres-erbsen/clock misses a nap there. afterFunc(d time.Duration, fn func()) // some tests want raw access to the clock. getClock() *clock.Mock } type runnerImpl struct { t *testing.T clock *clock.Mock constructor func(int, ...Option) Limiter count atomic.Int32 // maxDuration is the time we need to move into the future for a test. // It's populated automatically based on assertCountAt/afterFunc. maxDuration time.Duration doneCh chan struct{} wg sync.WaitGroup } func runTest(t *testing.T, fn func(testRunner)) { impls := []struct { name string constructor func(int, ...Option) Limiter }{ { name: "mutex", constructor: func(rate int, opts ...Option) Limiter { return newMutexBased(rate, opts...) }, }, { name: "atomic", constructor: func(rate int, opts ...Option) Limiter { return newAtomicBased(rate, opts...) }, }, { name: "atomic_int64", constructor: func(rate int, opts ...Option) Limiter { return newAtomicInt64Based(rate, opts...) }, }, } for _, tt := range impls { t.Run(tt.name, func(t *testing.T) { // Set a non-default time.Time since some limiters (int64 in particular) use // the default value as "non-initialized" state. clockMock := clock.NewMock() clockMock.Set(time.Now()) r := runnerImpl{ t: t, clock: clockMock, constructor: tt.constructor, doneCh: make(chan struct{}), } defer close(r.doneCh) defer r.wg.Wait() fn(&r) r.clock.Add(r.maxDuration) }) } } // createLimiter builds a limiter with given options. func (r *runnerImpl) createLimiter(rate int, opts ...Option) Limiter { opts = append(opts, WithClock(r.clock)) return r.constructor(rate, opts...) } func (r *runnerImpl) getClock() *clock.Mock { return r.clock } // startTaking tries to Take() on passed in limiters in a loop/goroutine. func (r *runnerImpl) startTaking(rls ...Limiter) { r.goWait(func() { for { for _, rl := range rls { rl.Take() } r.count.Inc() select { case <-r.doneCh: return default: } } }) } // takeOnceAfter attempts to Take at a specific time. func (r *runnerImpl) takeOnceAfter(d time.Duration, rl Limiter) { r.wg.Add(1) r.afterFunc(d, func() { rl.Take() r.count.Inc() r.wg.Done() }) } // assertCountAt asserts the limiters have Taken() a number of times at a given time. func (r *runnerImpl) assertCountAt(d time.Duration, count int) { r.wg.Add(1) r.afterFunc(d, func() { assert.Equal(r.t, int32(count), r.count.Load(), "count not as expected") r.wg.Done() }) } // afterFunc executes a func at a given time. func (r *runnerImpl) afterFunc(d time.Duration, fn func()) { if d > r.maxDuration { r.maxDuration = d } r.goWait(func() { select { case <-r.doneCh: return case <-r.clock.After(d): } fn() }) } // goWait runs a function in a goroutine and makes sure the goroutine was scheduled. func (r *runnerImpl) goWait(fn func()) { wg := sync.WaitGroup{} wg.Add(1) go func() { wg.Done() fn() }() wg.Wait() } func TestUnlimited(t *testing.T) { t.Parallel() now := time.Now() rl := NewUnlimited() for i := 0; i < 1000; i++ { rl.Take() } assert.Condition(t, func() bool { return time.Since(now) < 1*time.Millisecond }, "no artificial delay") } func TestRateLimiter(t *testing.T) { t.Parallel() runTest(t, func(r testRunner) { rl := r.createLimiter(100, WithoutSlack) // Create copious counts concurrently. r.startTaking(rl) r.startTaking(rl) r.startTaking(rl) r.startTaking(rl) r.assertCountAt(1*time.Second, 100) r.assertCountAt(2*time.Second, 200) r.assertCountAt(3*time.Second, 300) }) } func TestDelayedRateLimiter(t *testing.T) { t.Parallel() runTest(t, func(r testRunner) { slow := r.createLimiter(10, WithoutSlack) fast := r.createLimiter(100, WithoutSlack) r.startTaking(slow, fast) r.afterFunc(20*time.Second, func() { r.startTaking(fast) r.startTaking(fast) r.startTaking(fast) r.startTaking(fast) }) r.assertCountAt(30*time.Second, 1200) }) } func TestPer(t *testing.T) { t.Parallel() runTest(t, func(r testRunner) { rl := r.createLimiter(7, WithoutSlack, Per(time.Minute)) r.startTaking(rl) r.startTaking(rl) r.assertCountAt(1*time.Second, 1) r.assertCountAt(1*time.Minute, 8) r.assertCountAt(2*time.Minute, 15) }) } // TestInitial verifies that the initial sequence is scheduled as expected. func TestInitial(t *testing.T) { t.Parallel() tests := []struct { msg string opts []Option }{ { msg: "With Slack", }, { msg: "Without Slack", opts: []Option{WithoutSlack}, }, } for _, tt := range tests { t.Run(tt.msg, func(t *testing.T) { runTest(t, func(r testRunner) { rl := r.createLimiter(10, tt.opts...) var ( clk = r.getClock() prev = clk.Now() results = make(chan time.Time) have []time.Duration startWg sync.WaitGroup ) startWg.Add(3) for i := 0; i < 3; i++ { go func() { startWg.Done() results <- rl.Take() }() } startWg.Wait() clk.Add(time.Second) for i := 0; i < 3; i++ { ts := <-results have = append(have, ts.Sub(prev)) prev = ts } assert.Equal(t, []time.Duration{ 0, time.Millisecond * 100, time.Millisecond * 100, }, have, "bad timestamps for inital takes", ) }) }) } } func TestMaxSlack(t *testing.T) { t.Parallel() runTest(t, func(r testRunner) { rl := r.createLimiter(1, WithSlack(1)) r.takeOnceAfter(time.Nanosecond, rl) r.takeOnceAfter(2*time.Second+1*time.Nanosecond, rl) r.takeOnceAfter(2*time.Second+2*time.Nanosecond, rl) r.takeOnceAfter(2*time.Second+3*time.Nanosecond, rl) r.takeOnceAfter(2*time.Second+4*time.Nanosecond, rl) r.assertCountAt(3*time.Second, 3) r.assertCountAt(10*time.Second, 5) }) } func TestSlack(t *testing.T) { t.Parallel() // To simulate slack, we combine two limiters. // - First, we start a single goroutine with both of them, // during this time the slow limiter will dominate, // and allow the fast limiter to accumulate slack. // - After 2 seconds, we start another goroutine with // only the faster limiter. This will allow it to max out, // and consume all the slack. // - After 3 seconds, we look at the final result, and we expect, // a sum of: // - slower limiter running for 3 seconds // - faster limiter running for 1 second // - slack accumulated by the faster limiter during the two seconds. // it was blocked by slower limiter. tests := []struct { msg string opt []Option want int }{ { msg: "no option, defaults to 10", // 2*10 + 1*100 + 1*10 (slack) want: 130, }, { msg: "slack of 10, like default", opt: []Option{WithSlack(10)}, // 2*10 + 1*100 + 1*10 (slack) want: 130, }, { msg: "slack of 20", opt: []Option{WithSlack(20)}, // 2*10 + 1*100 + 1*20 (slack) want: 140, }, { // Note this is bigger then the rate of the limiter. msg: "slack of 150", opt: []Option{WithSlack(150)}, // 2*10 + 1*100 + 1*150 (slack) want: 270, }, { msg: "no option, defaults to 10, with per", // 2*(10*2) + 1*(100*2) + 1*10 (slack) opt: []Option{Per(500 * time.Millisecond)}, want: 230, }, { msg: "slack of 10, like default, with per", opt: []Option{WithSlack(10), Per(500 * time.Millisecond)}, // 2*(10*2) + 1*(100*2) + 1*10 (slack) want: 230, }, { msg: "slack of 20, with per", opt: []Option{WithSlack(20), Per(500 * time.Millisecond)}, // 2*(10*2) + 1*(100*2) + 1*20 (slack) want: 240, }, { // Note this is bigger then the rate of the limiter. msg: "slack of 150, with per", opt: []Option{WithSlack(150), Per(500 * time.Millisecond)}, // 2*(10*2) + 1*(100*2) + 1*150 (slack) want: 370, }, } for _, tt := range tests { t.Run(tt.msg, func(t *testing.T) { runTest(t, func(r testRunner) { slow := r.createLimiter(10, WithoutSlack) fast := r.createLimiter(100, tt.opt...) r.startTaking(slow, fast) r.afterFunc(2*time.Second, func() { r.startTaking(fast) r.startTaking(fast) }) // limiter with 10hz dominates here - we're always at 10. r.assertCountAt(1*time.Second, 10) r.assertCountAt(3*time.Second, tt.want) }) }) } } ratelimit-0.3.1/tools/000077500000000000000000000000001457140654600146615ustar00rootroot00000000000000ratelimit-0.3.1/tools/go.mod000066400000000000000000000010651457140654600157710ustar00rootroot00000000000000module go.uber.org/ratelimit/tools go 1.20 require ( github.com/storozhukBM/benchart v1.0.0 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 golang.org/x/perf v0.0.0-20230427221525-d343f6398b76 honnef.co/go/tools v0.4.3 ) require ( github.com/BurntSushi/toml v1.3.2 // indirect github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 // indirect golang.org/x/exp/typeparams v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/tools v0.11.0 // indirect ) ratelimit-0.3.1/tools/go.sum000066400000000000000000000274411457140654600160240ustar00rootroot00000000000000cloud.google.com/go v0.0.0-20170206221025-ce650573d812/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20190129172621-c8b1d7a94ddf/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo= github.com/aclements/go-gg v0.0.0-20170118225347-6dbb4e4fefb0/go.mod h1:55qNq4vcpkIuHowELi5C8e+1yUHtoLoOUR9QU5j7Tes= github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 h1:xlwdaKcTNVW4PtpQb8aKA4Pjy0CdJHEqvFbAnvR5m2g= github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794/go.mod h1:7e+I0LQFUI9AXWxOfsQROs9xPhoJtbsyWcjJqDd4KPY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/ajstarks/svgo v0.0.0-20210923152817-c3b6e2f0c527/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9/go.mod h1:XA3DeT6rxh2EAE789SSiSJNqxPaC0aE9J8NTOI0Jo/A= github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw= github.com/google/safehtml v0.0.2/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU= github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/storozhukBM/benchart v1.0.0 h1:AB7VOrqf0+rDh8RePGkXgGzQoORZLmQa8xL+XGb3mdw= github.com/storozhukBM/benchart v1.0.0/go.mod h1:on038YphXMAZ69Wg5JNU6UCpG8TH9x3JgKNq983qGUI= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp/typeparams v0.0.0-20230626212559-97b1e661b5df h1:jfUqBujZx2dktJVEmZpCkyngz7MWrVv1y9kLOqFNsqw= golang.org/x/exp/typeparams v0.0.0-20230626212559-97b1e661b5df/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20170207211851-4464e7848382/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/perf v0.0.0-20230427221525-d343f6398b76 h1:cPGZx8Liyx5Pq/yX80/6WMKe2yidT0xvVCQBOGa8WHU= golang.org/x/perf v0.0.0-20230427221525-d343f6398b76/go.mod h1:UBKtEnL8aqnd+0JHqZ+2qoMDwtuy6cYhhKNoHLBiTQc= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= 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= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= gonum.org/v1/plot v0.10.0/go.mod h1:JWIHJ7U20drSQb/aDpTetJzfC1KlAPldJLpkSy88dvQ= google.golang.org/api v0.0.0-20170206182103-3d017632ea10/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/grpc v0.0.0-20170208002647-2a6bf6142e96/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= honnef.co/go/tools v0.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA= honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= honnef.co/go/tools v0.4.3 h1:o/n5/K5gXqk8Gozvs2cnL0F2S1/g1vcGCAx2vETjITw= honnef.co/go/tools v0.4.3/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= ratelimit-0.3.1/tools/tools.go000066400000000000000000000025231457140654600163520ustar00rootroot00000000000000// 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 tools // +build tools package tools import ( // Tools we use during development. _ "github.com/storozhukBM/benchart" _ "golang.org/x/lint/golint" _ "golang.org/x/perf/cmd/benchstat" _ "honnef.co/go/tools/cmd/staticcheck" )