berater 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/berater/concurrency_limiter.rb +1 -1
- data/lib/berater/limiter.rb +7 -7
- data/lib/berater/middleware/statsd.rb +32 -22
- data/lib/berater/middleware/trace.rb +41 -0
- data/lib/berater/middleware.rb +1 -0
- data/lib/berater/rate_limiter.rb +1 -2
- data/lib/berater/static_limiter.rb +1 -1
- data/lib/berater/test_mode.rb +3 -1
- data/lib/berater/version.rb +1 -1
- data/lib/berater.rb +2 -2
- data/spec/berater_spec.rb +7 -4
- data/spec/limiter_spec.rb +32 -5
- data/spec/middleware/fail_open_spec.rb +2 -0
- data/spec/middleware/load_shedder_spec.rb +2 -0
- data/spec/middleware/statsd_spec.rb +81 -47
- data/spec/middleware/trace_spec.rb +58 -0
- data/spec/test_mode_spec.rb +32 -6
- metadata +19 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: defd1ba585ef0f8b5ff8f2f388604c344571fe4fe887f7ca44d03d57c7ce20f8
|
|
4
|
+
data.tar.gz: 729865914aa2f72c4b61a16cbcd29f12d86d034a04f9c15f3c48c53a7611c5cf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 864cc51b0eaf4c1767a36580331a27dfb20a2e0eeaaff805d2d188078cf9500ccfbf0d08e331cd06efb238b0c58dca9f67cde27025d1e87ebec8ef4ae234c683
|
|
7
|
+
data.tar.gz: aa303e6a58f1d79b0471d9d31bb5aa8835bd7f253c26f7930c6253e7801e154b9c26ce977529e70a7de647d9967caa6753963608828e721f7d1cfe5523805f41
|
data/lib/berater/limiter.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module Berater
|
|
2
2
|
class Limiter
|
|
3
|
+
DEFAULT_COST = 1
|
|
3
4
|
|
|
4
5
|
attr_reader :key, :capacity, :options
|
|
5
6
|
|
|
@@ -9,7 +10,7 @@ module Berater
|
|
|
9
10
|
|
|
10
11
|
def limit(**opts, &block)
|
|
11
12
|
opts[:capacity] ||= @capacity
|
|
12
|
-
opts[:cost] ||=
|
|
13
|
+
opts[:cost] ||= DEFAULT_COST
|
|
13
14
|
|
|
14
15
|
lock = Berater.middleware.call(self, **opts) do |limiter, **opts|
|
|
15
16
|
limiter.inner_limit(**opts)
|
|
@@ -26,7 +27,7 @@ module Berater
|
|
|
26
27
|
end
|
|
27
28
|
end
|
|
28
29
|
|
|
29
|
-
protected def inner_limit(capacity:, cost
|
|
30
|
+
protected def inner_limit(capacity:, cost:, **opts)
|
|
30
31
|
if capacity.is_a?(String)
|
|
31
32
|
# try casting
|
|
32
33
|
begin
|
|
@@ -49,7 +50,7 @@ module Berater
|
|
|
49
50
|
raise ArgumentError, "invalid cost: #{cost}"
|
|
50
51
|
end
|
|
51
52
|
|
|
52
|
-
acquire_lock(capacity, cost)
|
|
53
|
+
acquire_lock(capacity: capacity, cost: cost, **opts)
|
|
53
54
|
rescue NoMethodError => e
|
|
54
55
|
raise unless e.message.include?("undefined method `evalsha' for")
|
|
55
56
|
|
|
@@ -110,13 +111,12 @@ module Berater
|
|
|
110
111
|
@capacity = capacity
|
|
111
112
|
end
|
|
112
113
|
|
|
113
|
-
def acquire_lock(capacity
|
|
114
|
+
def acquire_lock(capacity:, cost:)
|
|
114
115
|
raise NotImplementedError
|
|
115
116
|
end
|
|
116
117
|
|
|
117
|
-
def cache_key
|
|
118
|
-
|
|
119
|
-
self.class.cache_key(instance_key)
|
|
118
|
+
def cache_key
|
|
119
|
+
self.class.cache_key(key)
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
class << self
|
|
@@ -10,7 +10,7 @@ module Berater
|
|
|
10
10
|
duration = -Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
11
11
|
lock = yield
|
|
12
12
|
rescue Exception => error
|
|
13
|
-
#
|
|
13
|
+
# capture exception for reporting, then propagate
|
|
14
14
|
raise
|
|
15
15
|
ensure
|
|
16
16
|
duration += Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
@@ -21,7 +21,7 @@ module Berater
|
|
|
21
21
|
@client.timing(
|
|
22
22
|
'berater.limiter.limit',
|
|
23
23
|
duration,
|
|
24
|
-
tags: tags
|
|
24
|
+
tags: tags,
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
@client.gauge(
|
|
@@ -30,31 +30,36 @@ module Berater
|
|
|
30
30
|
tags: tags,
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
-
if lock
|
|
34
|
-
@client.
|
|
35
|
-
'berater.lock.
|
|
36
|
-
lock.capacity,
|
|
37
|
-
tags: tags,
|
|
38
|
-
)
|
|
39
|
-
@client.gauge(
|
|
40
|
-
'berater.limiter.contention',
|
|
41
|
-
lock.contention,
|
|
33
|
+
if lock
|
|
34
|
+
@client.increment(
|
|
35
|
+
'berater.lock.acquired',
|
|
42
36
|
tags: tags,
|
|
43
37
|
)
|
|
38
|
+
|
|
39
|
+
if lock.contention >= 0 # not a failsafe lock
|
|
40
|
+
@client.gauge(
|
|
41
|
+
'berater.lock.capacity',
|
|
42
|
+
lock.capacity,
|
|
43
|
+
tags: tags,
|
|
44
|
+
)
|
|
45
|
+
@client.gauge(
|
|
46
|
+
'berater.lock.contention',
|
|
47
|
+
lock.contention,
|
|
48
|
+
tags: tags,
|
|
49
|
+
)
|
|
50
|
+
end
|
|
44
51
|
end
|
|
45
52
|
|
|
46
53
|
if error
|
|
47
54
|
if error.is_a?(Berater::Overloaded)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
'berater.limiter.contention',
|
|
51
|
-
limiter.capacity,
|
|
55
|
+
@client.increment(
|
|
56
|
+
'berater.limiter.overloaded',
|
|
52
57
|
tags: tags,
|
|
53
58
|
)
|
|
54
59
|
else
|
|
55
60
|
@client.increment(
|
|
56
61
|
'berater.limiter.error',
|
|
57
|
-
tags: tags.merge(type: error.class.to_s)
|
|
62
|
+
tags: tags.merge(type: error.class.to_s.gsub('::', '_'))
|
|
58
63
|
)
|
|
59
64
|
end
|
|
60
65
|
end
|
|
@@ -68,14 +73,19 @@ module Berater
|
|
|
68
73
|
limiter: limiter.class.to_s.split(':')[-1],
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
# append custom tags
|
|
72
|
-
if @tags
|
|
73
|
-
tags.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
# append global custom tags
|
|
77
|
+
if @tags
|
|
78
|
+
if @tags.respond_to?(:call)
|
|
79
|
+
tags.merge!(@tags.call(limiter, **opts) || {})
|
|
80
|
+
else
|
|
81
|
+
tags.merge!(@tags)
|
|
82
|
+
end
|
|
76
83
|
end
|
|
77
84
|
|
|
78
|
-
tags
|
|
85
|
+
# append call specific custom tags
|
|
86
|
+
tags.merge!(opts[:tags]) if opts[:tags]
|
|
87
|
+
|
|
88
|
+
tags
|
|
79
89
|
end
|
|
80
90
|
end
|
|
81
91
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'ddtrace'
|
|
2
|
+
|
|
3
|
+
module Berater
|
|
4
|
+
module Middleware
|
|
5
|
+
class Trace
|
|
6
|
+
def initialize(tracer: nil)
|
|
7
|
+
@tracer = tracer
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call(limiter, **)
|
|
11
|
+
tracer.trace('Berater.limit') do |span|
|
|
12
|
+
begin
|
|
13
|
+
lock = yield
|
|
14
|
+
rescue Exception => error
|
|
15
|
+
# capture exception for reporting, then propagate
|
|
16
|
+
raise
|
|
17
|
+
ensure
|
|
18
|
+
span.set_tag('capacity', limiter.capacity)
|
|
19
|
+
span.set_tag('contention', lock.contention) if lock
|
|
20
|
+
span.set_tag('key', limiter.key)
|
|
21
|
+
span.set_tag('limiter', limiter.class.to_s.split(':')[-1])
|
|
22
|
+
|
|
23
|
+
if error
|
|
24
|
+
if error.is_a?(Berater::Overloaded)
|
|
25
|
+
span.set_tag('overloaded', true)
|
|
26
|
+
else
|
|
27
|
+
span.set_tag('error', error.class.to_s.gsub('::', '_'))
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def tracer
|
|
37
|
+
@tracer || Datadog.tracer
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/berater/middleware.rb
CHANGED
data/lib/berater/rate_limiter.rb
CHANGED
|
@@ -20,7 +20,6 @@ module Berater
|
|
|
20
20
|
|
|
21
21
|
LUA_SCRIPT = Berater::LuaScript(<<~LUA
|
|
22
22
|
local key = KEYS[1]
|
|
23
|
-
local ts_key = KEYS[2]
|
|
24
23
|
local ts = tonumber(ARGV[1])
|
|
25
24
|
local capacity = tonumber(ARGV[2])
|
|
26
25
|
local interval_msec = tonumber(ARGV[3])
|
|
@@ -71,7 +70,7 @@ module Berater
|
|
|
71
70
|
LUA
|
|
72
71
|
)
|
|
73
72
|
|
|
74
|
-
protected def acquire_lock(capacity
|
|
73
|
+
protected def acquire_lock(capacity:, cost:)
|
|
75
74
|
# timestamp in milliseconds
|
|
76
75
|
ts = (Time.now.to_f * 10**3).to_i
|
|
77
76
|
|
data/lib/berater/test_mode.rb
CHANGED
|
@@ -21,7 +21,7 @@ module Berater
|
|
|
21
21
|
|
|
22
22
|
class Limiter
|
|
23
23
|
module TestMode
|
|
24
|
-
def acquire_lock(
|
|
24
|
+
def acquire_lock(**)
|
|
25
25
|
case Berater.test_mode
|
|
26
26
|
when :pass
|
|
27
27
|
Lock.new(Float::INFINITY, 0)
|
|
@@ -43,5 +43,7 @@ Berater.singleton_class.prepend Berater::TestMode
|
|
|
43
43
|
ObjectSpace.each_object(Class).each do |klass|
|
|
44
44
|
next unless klass < Berater::Limiter
|
|
45
45
|
|
|
46
|
+
next if klass == Berater::Unlimiter || klass == Berater::Inhibitor
|
|
47
|
+
|
|
46
48
|
klass.prepend Berater::Limiter::TestMode
|
|
47
49
|
end
|
data/lib/berater/version.rb
CHANGED
data/lib/berater.rb
CHANGED
data/spec/berater_spec.rb
CHANGED
|
@@ -77,18 +77,21 @@ describe Berater do
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
context 'with a block' do
|
|
80
|
-
before { Berater.test_mode = :pass }
|
|
81
|
-
|
|
82
80
|
subject { Berater(:key, capacity, **opts) { 123 } }
|
|
83
81
|
|
|
84
82
|
it 'creates a limiter and calls limit' do
|
|
85
83
|
expect(klass).to receive(:new).and_return(limiter)
|
|
86
|
-
expect(limiter).to receive(:limit)
|
|
84
|
+
expect(limiter).to receive(:limit)
|
|
85
|
+
|
|
87
86
|
subject
|
|
88
87
|
end
|
|
89
88
|
|
|
90
89
|
it 'yields' do
|
|
91
|
-
|
|
90
|
+
if capacity > 0
|
|
91
|
+
is_expected.to be 123
|
|
92
|
+
else
|
|
93
|
+
expect { subject }.to be_overloaded
|
|
94
|
+
end
|
|
92
95
|
end
|
|
93
96
|
end
|
|
94
97
|
end
|
data/spec/limiter_spec.rb
CHANGED
|
@@ -47,7 +47,9 @@ describe Berater::Limiter do
|
|
|
47
47
|
|
|
48
48
|
context 'with a capacity parameter' do
|
|
49
49
|
it 'overrides the stored value' do
|
|
50
|
-
is_expected.to receive(:acquire_lock).with(
|
|
50
|
+
is_expected.to receive(:acquire_lock).with(hash_including(
|
|
51
|
+
capacity: 3,
|
|
52
|
+
))
|
|
51
53
|
|
|
52
54
|
subject.limit(capacity: 3)
|
|
53
55
|
end
|
|
@@ -59,15 +61,29 @@ describe Berater::Limiter do
|
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
it 'handles stringified numerics gracefully' do
|
|
62
|
-
is_expected.to receive(:acquire_lock).with(
|
|
64
|
+
is_expected.to receive(:acquire_lock).with(hash_including(
|
|
65
|
+
capacity: 3.5,
|
|
66
|
+
))
|
|
63
67
|
|
|
64
68
|
subject.limit(capacity: '3.5')
|
|
65
69
|
end
|
|
66
70
|
end
|
|
67
71
|
|
|
72
|
+
it 'has a default cost' do
|
|
73
|
+
is_expected.to receive(:acquire_lock).with(hash_including(
|
|
74
|
+
cost: described_class::DEFAULT_COST,
|
|
75
|
+
))
|
|
76
|
+
|
|
77
|
+
subject.limit
|
|
78
|
+
end
|
|
79
|
+
|
|
68
80
|
context 'with a cost parameter' do
|
|
69
|
-
it 'overrides the
|
|
70
|
-
|
|
81
|
+
it 'overrides the default value' do
|
|
82
|
+
expect(described_class::DEFAULT_COST).not_to eq 2
|
|
83
|
+
|
|
84
|
+
is_expected.to receive(:acquire_lock).with(hash_including(
|
|
85
|
+
cost: 2,
|
|
86
|
+
))
|
|
71
87
|
|
|
72
88
|
subject.limit(cost: 2)
|
|
73
89
|
end
|
|
@@ -87,12 +103,23 @@ describe Berater::Limiter do
|
|
|
87
103
|
end
|
|
88
104
|
|
|
89
105
|
it 'handles stringified numerics gracefully' do
|
|
90
|
-
is_expected.to receive(:acquire_lock).with(
|
|
106
|
+
is_expected.to receive(:acquire_lock).with(hash_including(
|
|
107
|
+
cost: 2.5,
|
|
108
|
+
))
|
|
91
109
|
|
|
92
110
|
subject.limit(cost: '2.5')
|
|
93
111
|
end
|
|
94
112
|
end
|
|
95
113
|
|
|
114
|
+
it 'passes through arbitrary parameters' do
|
|
115
|
+
is_expected.to receive(:acquire_lock).with(hash_including(
|
|
116
|
+
priority: 123,
|
|
117
|
+
zed: nil,
|
|
118
|
+
))
|
|
119
|
+
|
|
120
|
+
subject.limit(priority: 123, zed: nil)
|
|
121
|
+
end
|
|
122
|
+
|
|
96
123
|
context 'when Berater.redis is nil' do
|
|
97
124
|
let!(:redis) { Berater.redis }
|
|
98
125
|
|
|
@@ -9,13 +9,23 @@ describe Berater::Middleware::Statsd do
|
|
|
9
9
|
allow(client).to receive(:timing)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
context do
|
|
13
|
+
before do
|
|
14
|
+
Berater.middleware.use described_class, client
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it_behaves_like 'a limiter middleware'
|
|
18
|
+
end
|
|
19
|
+
|
|
12
20
|
describe '#call' do
|
|
13
|
-
subject
|
|
21
|
+
subject do
|
|
22
|
+
described_class.new(client, **middleware_opts).call(limiter, **opts, &block)
|
|
23
|
+
end
|
|
14
24
|
|
|
15
|
-
let(:
|
|
25
|
+
let(:middleware_opts) { {} }
|
|
16
26
|
let(:limiter) { double(Berater::Limiter, key: :key, capacity: 5) }
|
|
17
27
|
let(:lock) { double(Berater::Lock, capacity: 4, contention: 2) }
|
|
18
|
-
let(:opts) { { capacity:
|
|
28
|
+
let(:opts) { { capacity: limiter.capacity, cost: 1 } }
|
|
19
29
|
let(:block) { lambda { lock } }
|
|
20
30
|
|
|
21
31
|
after { subject }
|
|
@@ -31,7 +41,6 @@ describe Berater::Middleware::Statsd do
|
|
|
31
41
|
tags: {
|
|
32
42
|
key: limiter.key,
|
|
33
43
|
limiter: String,
|
|
34
|
-
overloaded: false,
|
|
35
44
|
},
|
|
36
45
|
)
|
|
37
46
|
end
|
|
@@ -44,39 +53,63 @@ describe Berater::Middleware::Statsd do
|
|
|
44
53
|
)
|
|
45
54
|
end
|
|
46
55
|
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
it 'tracks lock acquisition' do
|
|
57
|
+
expect(client).to receive(:increment).with(
|
|
58
|
+
'berater.lock.acquired',
|
|
59
|
+
Hash
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'tracks lock capacity' do
|
|
64
|
+
expect(client).to receive(:gauge).with(
|
|
65
|
+
'berater.lock.capacity',
|
|
66
|
+
lock.capacity,
|
|
67
|
+
Hash
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'tracks lock contention' do
|
|
72
|
+
expect(client).to receive(:gauge).with(
|
|
73
|
+
'berater.lock.contention',
|
|
74
|
+
lock.contention,
|
|
75
|
+
Hash
|
|
76
|
+
)
|
|
77
|
+
end
|
|
49
78
|
|
|
50
|
-
|
|
79
|
+
describe 'tags' do
|
|
80
|
+
def expect_tags_to(matcher)
|
|
51
81
|
expect(client).to receive(:timing) do |*, tags:|
|
|
52
|
-
expect(tags).to
|
|
82
|
+
expect(tags).to matcher
|
|
53
83
|
end
|
|
54
84
|
end
|
|
55
|
-
end
|
|
56
85
|
|
|
57
|
-
|
|
58
|
-
|
|
86
|
+
context 'with global tags' do
|
|
87
|
+
let(:middleware_opts) { { tags: { abc: 123 } }}
|
|
59
88
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
expect(tags).to include(client_opts[:tags])
|
|
89
|
+
it 'incorporates the tags' do
|
|
90
|
+
expect_tags_to include(middleware_opts[:tags])
|
|
63
91
|
end
|
|
64
92
|
end
|
|
65
|
-
end
|
|
66
93
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
94
|
+
context 'with global tag callback' do
|
|
95
|
+
let(:middleware_opts) { { tags: callback }}
|
|
96
|
+
let(:callback) { double(Proc) }
|
|
97
|
+
|
|
98
|
+
it 'calls the callback' do
|
|
99
|
+
expect(callback).to receive(:call).with(limiter, **opts)
|
|
100
|
+
end
|
|
70
101
|
|
|
71
|
-
|
|
72
|
-
|
|
102
|
+
it 'incorporates the tags' do
|
|
103
|
+
expect(callback).to receive(:call).and_return({ abc: 123 })
|
|
104
|
+
expect_tags_to include(abc: 123)
|
|
105
|
+
end
|
|
73
106
|
end
|
|
74
107
|
|
|
75
|
-
|
|
76
|
-
|
|
108
|
+
context 'when call specific custom tags are passed in' do
|
|
109
|
+
let(:opts) { { tags: { abc: 123 } } }
|
|
77
110
|
|
|
78
|
-
|
|
79
|
-
|
|
111
|
+
it 'incorporates the tags' do
|
|
112
|
+
expect_tags_to include(opts[:tags])
|
|
80
113
|
end
|
|
81
114
|
end
|
|
82
115
|
end
|
|
@@ -101,7 +134,13 @@ describe Berater::Middleware::Statsd do
|
|
|
101
134
|
)
|
|
102
135
|
|
|
103
136
|
expect(client).to receive(:gauge).with(
|
|
104
|
-
'berater.
|
|
137
|
+
'berater.lock.capacity',
|
|
138
|
+
limiter.capacity,
|
|
139
|
+
Hash,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
expect(client).to receive(:gauge).with(
|
|
143
|
+
'berater.lock.contention',
|
|
105
144
|
1,
|
|
106
145
|
Hash,
|
|
107
146
|
)
|
|
@@ -111,13 +150,13 @@ describe Berater::Middleware::Statsd do
|
|
|
111
150
|
|
|
112
151
|
it 'tracks each call' do
|
|
113
152
|
expect(client).to receive(:gauge).with(
|
|
114
|
-
'berater.
|
|
153
|
+
'berater.lock.contention',
|
|
115
154
|
1,
|
|
116
155
|
Hash,
|
|
117
156
|
)
|
|
118
157
|
|
|
119
158
|
expect(client).to receive(:gauge).with(
|
|
120
|
-
'berater.
|
|
159
|
+
'berater.lock.contention',
|
|
121
160
|
2,
|
|
122
161
|
Hash,
|
|
123
162
|
)
|
|
@@ -135,7 +174,7 @@ describe Berater::Middleware::Statsd do
|
|
|
135
174
|
it 'tracks limiter exceptions' do
|
|
136
175
|
expect(client).to receive(:increment).with(
|
|
137
176
|
'berater.limiter.error',
|
|
138
|
-
tags: hash_including(type:
|
|
177
|
+
tags: hash_including(type: 'Redis_TimeoutError'),
|
|
139
178
|
)
|
|
140
179
|
|
|
141
180
|
expect { limiter.limit }.to raise_error(error)
|
|
@@ -149,6 +188,7 @@ describe Berater::Middleware::Statsd do
|
|
|
149
188
|
it 'does not track the exception' do
|
|
150
189
|
expect(client).not_to receive(:increment).with(
|
|
151
190
|
'berater.limiter.error',
|
|
191
|
+
anything,
|
|
152
192
|
)
|
|
153
193
|
|
|
154
194
|
limiter.limit
|
|
@@ -156,11 +196,8 @@ describe Berater::Middleware::Statsd do
|
|
|
156
196
|
|
|
157
197
|
it 'does not track lock-based stats' do
|
|
158
198
|
expect(client).not_to receive(:gauge).with(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
expect(client).not_to receive(:gauge).with(
|
|
163
|
-
'berater.limiter.contention',
|
|
199
|
+
/berater.lock/,
|
|
200
|
+
any_args,
|
|
164
201
|
)
|
|
165
202
|
|
|
166
203
|
limiter.limit
|
|
@@ -175,7 +212,7 @@ describe Berater::Middleware::Statsd do
|
|
|
175
212
|
it 'tracks the exception' do
|
|
176
213
|
expect(client).to receive(:increment).with(
|
|
177
214
|
'berater.limiter.error',
|
|
178
|
-
|
|
215
|
+
anything,
|
|
179
216
|
)
|
|
180
217
|
|
|
181
218
|
limiter.limit
|
|
@@ -183,11 +220,8 @@ describe Berater::Middleware::Statsd do
|
|
|
183
220
|
|
|
184
221
|
it 'does not track lock-based stats' do
|
|
185
222
|
expect(client).not_to receive(:gauge).with(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
expect(client).not_to receive(:gauge).with(
|
|
190
|
-
'berater.limiter.contention',
|
|
223
|
+
/berater.lock/,
|
|
224
|
+
any_args,
|
|
191
225
|
)
|
|
192
226
|
|
|
193
227
|
limiter.limit
|
|
@@ -202,24 +236,24 @@ describe Berater::Middleware::Statsd do
|
|
|
202
236
|
expect { limiter.limit }.to be_overloaded
|
|
203
237
|
end
|
|
204
238
|
|
|
205
|
-
it 'tracks the
|
|
206
|
-
expect(client).to receive(:
|
|
207
|
-
'berater.limiter.
|
|
208
|
-
|
|
209
|
-
tags: hash_including(overloaded: true),
|
|
239
|
+
it 'tracks the overloaded count' do
|
|
240
|
+
expect(client).to receive(:increment).with(
|
|
241
|
+
'berater.limiter.overloaded',
|
|
242
|
+
Hash
|
|
210
243
|
)
|
|
211
244
|
end
|
|
212
245
|
|
|
213
|
-
it '
|
|
246
|
+
it 'does not track lock-based stats' do
|
|
214
247
|
expect(client).not_to receive(:gauge).with(
|
|
215
|
-
|
|
216
|
-
|
|
248
|
+
/berater.lock/,
|
|
249
|
+
any_args,
|
|
217
250
|
)
|
|
218
251
|
end
|
|
219
252
|
|
|
220
253
|
it 'does not track the exception' do
|
|
221
254
|
expect(client).not_to receive(:increment).with(
|
|
222
255
|
'berater.limiter.error',
|
|
256
|
+
anything,
|
|
223
257
|
)
|
|
224
258
|
end
|
|
225
259
|
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
describe Berater::Middleware::Trace do
|
|
2
|
+
before { Datadog.tracer.enabled = false }
|
|
3
|
+
|
|
4
|
+
it_behaves_like 'a limiter middleware'
|
|
5
|
+
|
|
6
|
+
let(:limiter) { Berater::Unlimiter.new }
|
|
7
|
+
let(:span) { double(Datadog::Span, set_tag: nil) }
|
|
8
|
+
let(:tracer) { double(Datadog::Tracer) }
|
|
9
|
+
|
|
10
|
+
before do
|
|
11
|
+
allow(tracer).to receive(:trace) {|&b| b.call(span) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context 'with a provided tracer' do
|
|
15
|
+
let(:instance) { described_class.new(tracer: tracer) }
|
|
16
|
+
|
|
17
|
+
it 'traces' do
|
|
18
|
+
expect(tracer).to receive(:trace).with(/Berater/)
|
|
19
|
+
expect(span).to receive(:set_tag).with(/capacity/, Numeric)
|
|
20
|
+
|
|
21
|
+
instance.call(limiter) {}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'yields' do
|
|
25
|
+
expect {|b| instance.call(limiter, &b) }.to yield_control
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context 'when an exception is raised' do
|
|
29
|
+
it 'tags the span and raises' do
|
|
30
|
+
expect(span).to receive(:set_tag).with('error', 'IOError')
|
|
31
|
+
|
|
32
|
+
expect {
|
|
33
|
+
instance.call(limiter) { raise IOError }
|
|
34
|
+
}.to raise_error(IOError)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context 'when an Overloaded exception is raised' do
|
|
39
|
+
let(:limiter) { Berater::Inhibitor.new }
|
|
40
|
+
|
|
41
|
+
it 'tags the span as overloaded and raises' do
|
|
42
|
+
expect(span).to receive(:set_tag).with('overloaded', true)
|
|
43
|
+
|
|
44
|
+
expect {
|
|
45
|
+
instance.call(limiter) { raise Berater::Overloaded }
|
|
46
|
+
}.to be_overloaded
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context 'with the default tracer' do
|
|
52
|
+
it 'uses Datadog.tracer' do
|
|
53
|
+
expect(Datadog).to receive(:tracer).and_return(tracer)
|
|
54
|
+
|
|
55
|
+
described_class.new.call(limiter) {}
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/spec/test_mode_spec.rb
CHANGED
|
@@ -9,8 +9,13 @@ describe Berater::TestMode do
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
it 'prepends Limiter subclasses' do
|
|
12
|
-
expect(Berater::
|
|
13
|
-
expect(Berater::
|
|
12
|
+
expect(Berater::ConcurrencyLimiter.ancestors).to include(Berater::Limiter::TestMode)
|
|
13
|
+
expect(Berater::RateLimiter.ancestors).to include(Berater::Limiter::TestMode)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'does not modify Unlimiter or Inhibitor' do
|
|
17
|
+
expect(Berater::Unlimiter.ancestors).not_to include(Berater::Limiter::TestMode)
|
|
18
|
+
expect(Berater::Inhibitor.ancestors).not_to include(Berater::Limiter::TestMode)
|
|
14
19
|
end
|
|
15
20
|
|
|
16
21
|
it 'preserves the original functionality via super' do
|
|
@@ -19,6 +24,8 @@ describe Berater::TestMode do
|
|
|
19
24
|
end
|
|
20
25
|
|
|
21
26
|
describe '.test_mode' do
|
|
27
|
+
let(:limiter) { Berater::ConcurrencyLimiter.new(:key, 1) }
|
|
28
|
+
|
|
22
29
|
it 'can be turned on' do
|
|
23
30
|
Berater.test_mode = :pass
|
|
24
31
|
expect(Berater.test_mode).to be :pass
|
|
@@ -37,7 +44,6 @@ describe Berater::TestMode do
|
|
|
37
44
|
end
|
|
38
45
|
|
|
39
46
|
it 'works no matter when limiter was created' do
|
|
40
|
-
limiter = Berater::Unlimiter.new
|
|
41
47
|
expect(limiter).not_to be_overloaded
|
|
42
48
|
|
|
43
49
|
Berater.test_mode = :fail
|
|
@@ -47,7 +53,7 @@ describe Berater::TestMode do
|
|
|
47
53
|
it 'supports a generic expectation' do
|
|
48
54
|
Berater.test_mode = :pass
|
|
49
55
|
expect_any_instance_of(Berater::Limiter).to receive(:limit)
|
|
50
|
-
|
|
56
|
+
limiter.limit
|
|
51
57
|
end
|
|
52
58
|
end
|
|
53
59
|
|
|
@@ -94,7 +100,17 @@ describe Berater::TestMode do
|
|
|
94
100
|
it_behaves_like 'it is not overloaded'
|
|
95
101
|
end
|
|
96
102
|
|
|
97
|
-
|
|
103
|
+
context 'when test_mode = :pass' do
|
|
104
|
+
before { Berater.test_mode = :pass }
|
|
105
|
+
|
|
106
|
+
it_behaves_like 'it is not overloaded'
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context 'when test_mode = :fail' do
|
|
110
|
+
before { Berater.test_mode = :fail }
|
|
111
|
+
|
|
112
|
+
it_behaves_like 'it is not overloaded'
|
|
113
|
+
end
|
|
98
114
|
end
|
|
99
115
|
|
|
100
116
|
describe 'Inhibitor' do
|
|
@@ -106,7 +122,17 @@ describe Berater::TestMode do
|
|
|
106
122
|
it_behaves_like 'it is overloaded'
|
|
107
123
|
end
|
|
108
124
|
|
|
109
|
-
|
|
125
|
+
context 'when test_mode = :pass' do
|
|
126
|
+
before { Berater.test_mode = :pass }
|
|
127
|
+
|
|
128
|
+
it_behaves_like 'it is overloaded'
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
context 'when test_mode = :fail' do
|
|
132
|
+
before { Berater.test_mode = :fail }
|
|
133
|
+
|
|
134
|
+
it_behaves_like 'it is overloaded'
|
|
135
|
+
end
|
|
110
136
|
end
|
|
111
137
|
|
|
112
138
|
describe 'RateLimiter' do
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: berater
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Pepper
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-03-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: meddleware
|
|
@@ -80,6 +80,20 @@ dependencies:
|
|
|
80
80
|
- - ">="
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: ddtrace
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
83
97
|
- !ruby/object:Gem::Dependency
|
|
84
98
|
name: dogstatsd-ruby
|
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -168,6 +182,7 @@ files:
|
|
|
168
182
|
- lib/berater/middleware/fail_open.rb
|
|
169
183
|
- lib/berater/middleware/load_shedder.rb
|
|
170
184
|
- lib/berater/middleware/statsd.rb
|
|
185
|
+
- lib/berater/middleware/trace.rb
|
|
171
186
|
- lib/berater/rate_limiter.rb
|
|
172
187
|
- lib/berater/rspec.rb
|
|
173
188
|
- lib/berater/rspec/matchers.rb
|
|
@@ -188,6 +203,7 @@ files:
|
|
|
188
203
|
- spec/middleware/fail_open_spec.rb
|
|
189
204
|
- spec/middleware/load_shedder_spec.rb
|
|
190
205
|
- spec/middleware/statsd_spec.rb
|
|
206
|
+
- spec/middleware/trace_spec.rb
|
|
191
207
|
- spec/middleware_spec.rb
|
|
192
208
|
- spec/rate_limiter_spec.rb
|
|
193
209
|
- spec/riddle_spec.rb
|
|
@@ -222,6 +238,7 @@ test_files:
|
|
|
222
238
|
- spec/rate_limiter_spec.rb
|
|
223
239
|
- spec/middleware/load_shedder_spec.rb
|
|
224
240
|
- spec/middleware/statsd_spec.rb
|
|
241
|
+
- spec/middleware/trace_spec.rb
|
|
225
242
|
- spec/middleware/fail_open_spec.rb
|
|
226
243
|
- spec/matchers_spec.rb
|
|
227
244
|
- spec/dsl_refinement_spec.rb
|