berater 0.12.1 → 0.13.1
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/load_shedder.rb +7 -6
- data/lib/berater/middleware/statsd.rb +14 -16
- data/lib/berater/middleware/trace.rb +39 -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/spec/berater_spec.rb +7 -4
- data/spec/limiter_spec.rb +35 -12
- data/spec/middleware/fail_open_spec.rb +2 -0
- data/spec/middleware/load_shedder_spec.rb +2 -0
- data/spec/middleware/statsd_spec.rb +68 -39
- data/spec/middleware/trace_spec.rb +96 -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: 1270edae5d2c019ae92b623ff4c26e588cf8d5413c8f7b6c549d3d3ec4e16766
|
4
|
+
data.tar.gz: a9a659acde5a04263b1ca516a2fb1ecdd760d9133f3603b4fa7ec336c06f59df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 07fc3784c2e6d41648f97878e3a0c62e3a089c1ef69315e88141642350180634c65a89deda082ed5933a90666c620111329756d0699d3fde5238b97d0ad37e82
|
7
|
+
data.tar.gz: 6f8dc42b5b694a45c286b1d66a0b8efbf17371b3fd6bfd8936098b4155b85ae6523146fecfc6b7c46e3b0a8df1ddb03158f1fd181c9e6a1e13042232089e1a41
|
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
|
@@ -23,13 +23,14 @@ module Berater
|
|
23
23
|
priority = Float(priority) rescue nil
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
if PRIORITY_RANGE.include?(priority)
|
27
|
+
# priority 1 stays at 100%, 2 scales down to 90%, 5 to 60%
|
28
|
+
factor = 1 - (priority - 1) * 0.1
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
(capacity * factor).floor
|
31
|
+
else
|
32
|
+
capacity
|
33
|
+
end
|
33
34
|
end
|
34
35
|
end
|
35
36
|
end
|
@@ -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)
|
@@ -36,14 +36,14 @@ module Berater
|
|
36
36
|
tags: tags,
|
37
37
|
)
|
38
38
|
|
39
|
-
if lock.contention
|
39
|
+
if lock.contention >= 0 # not a failsafe lock
|
40
40
|
@client.gauge(
|
41
41
|
'berater.lock.capacity',
|
42
42
|
lock.capacity,
|
43
43
|
tags: tags,
|
44
44
|
)
|
45
45
|
@client.gauge(
|
46
|
-
'berater.
|
46
|
+
'berater.lock.contention',
|
47
47
|
lock.contention,
|
48
48
|
tags: tags,
|
49
49
|
)
|
@@ -56,13 +56,6 @@ module Berater
|
|
56
56
|
'berater.limiter.overloaded',
|
57
57
|
tags: tags,
|
58
58
|
)
|
59
|
-
|
60
|
-
# overloaded, so contention >= capacity
|
61
|
-
@client.gauge(
|
62
|
-
'berater.limiter.contention',
|
63
|
-
limiter.capacity,
|
64
|
-
tags: tags,
|
65
|
-
)
|
66
59
|
else
|
67
60
|
@client.increment(
|
68
61
|
'berater.limiter.error',
|
@@ -80,14 +73,19 @@ module Berater
|
|
80
73
|
limiter: limiter.class.to_s.split(':')[-1],
|
81
74
|
}
|
82
75
|
|
83
|
-
# append custom tags
|
84
|
-
if @tags
|
85
|
-
tags.
|
86
|
-
|
87
|
-
|
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
|
88
83
|
end
|
89
84
|
|
90
|
-
tags
|
85
|
+
# append call specific custom tags
|
86
|
+
tags.merge!(opts[:tags]) if opts[:tags]
|
87
|
+
|
88
|
+
tags
|
91
89
|
end
|
92
90
|
end
|
93
91
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Berater
|
2
|
+
module Middleware
|
3
|
+
class Trace
|
4
|
+
def initialize(tracer: nil)
|
5
|
+
@tracer = tracer
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(limiter, **)
|
9
|
+
tracer&.trace('Berater') do |span|
|
10
|
+
begin
|
11
|
+
lock = yield
|
12
|
+
rescue Exception => error
|
13
|
+
# capture exception for reporting, then propagate
|
14
|
+
raise
|
15
|
+
ensure
|
16
|
+
span.set_tag('capacity', limiter.capacity)
|
17
|
+
span.set_tag('contention', lock.contention) if lock
|
18
|
+
span.set_tag('key', limiter.key)
|
19
|
+
span.set_tag('limiter', limiter.class.to_s.split(':')[-1])
|
20
|
+
|
21
|
+
if error
|
22
|
+
if error.is_a?(Berater::Overloaded)
|
23
|
+
span.set_tag('overloaded', true)
|
24
|
+
else
|
25
|
+
span.set_tag('error', error.class.to_s.gsub('::', '_'))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def tracer
|
35
|
+
@tracer || (defined?(Datadog::Tracing) && Datadog::Tracing) || (defined?(Datadog) && Datadog.tracer)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
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/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
|
|
@@ -193,9 +220,7 @@ describe Berater::Limiter do
|
|
193
220
|
end
|
194
221
|
|
195
222
|
context 'with custom limiter' do
|
196
|
-
MyLimiter = Class.new(Berater::Unlimiter)
|
197
|
-
|
198
|
-
let(:klass) { MyLimiter }
|
223
|
+
let(:klass) { MyLimiter = Class.new(Berater::Unlimiter) }
|
199
224
|
|
200
225
|
it 'adds Berater prefix' do
|
201
226
|
is_expected.to eq 'Berater:MyLimiter:key'
|
@@ -215,12 +240,10 @@ describe Berater::Limiter do
|
|
215
240
|
end
|
216
241
|
|
217
242
|
context 'with custom limiter' do
|
218
|
-
|
219
|
-
|
220
|
-
let(:klass) { MyLimiter }
|
243
|
+
let(:klass) { MyOtherLimiter = Class.new(Berater::Unlimiter) }
|
221
244
|
|
222
245
|
it 'adds Berater prefix' do
|
223
|
-
is_expected.to eq 'Berater:
|
246
|
+
is_expected.to eq 'Berater:MyOtherLimiter:key'
|
224
247
|
end
|
225
248
|
end
|
226
249
|
end
|
@@ -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 }
|
@@ -50,39 +60,56 @@ describe Berater::Middleware::Statsd do
|
|
50
60
|
)
|
51
61
|
end
|
52
62
|
|
53
|
-
|
54
|
-
|
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
|
55
78
|
|
56
|
-
|
79
|
+
describe 'tags' do
|
80
|
+
def expect_tags_to(matcher)
|
57
81
|
expect(client).to receive(:timing) do |*, tags:|
|
58
|
-
expect(tags).to
|
82
|
+
expect(tags).to matcher
|
59
83
|
end
|
60
84
|
end
|
61
|
-
end
|
62
85
|
|
63
|
-
|
64
|
-
|
86
|
+
context 'with global tags' do
|
87
|
+
let(:middleware_opts) { { tags: { abc: 123 } }}
|
65
88
|
|
66
|
-
|
67
|
-
|
68
|
-
expect(tags).to include(client_opts[:tags])
|
89
|
+
it 'incorporates the tags' do
|
90
|
+
expect_tags_to include(middleware_opts[:tags])
|
69
91
|
end
|
70
92
|
end
|
71
|
-
end
|
72
93
|
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
76
101
|
|
77
|
-
|
78
|
-
|
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
|
79
106
|
end
|
80
107
|
|
81
|
-
|
82
|
-
|
108
|
+
context 'when call specific custom tags are passed in' do
|
109
|
+
let(:opts) { { tags: { abc: 123 } } }
|
83
110
|
|
84
|
-
|
85
|
-
|
111
|
+
it 'incorporates the tags' do
|
112
|
+
expect_tags_to include(opts[:tags])
|
86
113
|
end
|
87
114
|
end
|
88
115
|
end
|
@@ -107,7 +134,13 @@ describe Berater::Middleware::Statsd do
|
|
107
134
|
)
|
108
135
|
|
109
136
|
expect(client).to receive(:gauge).with(
|
110
|
-
'berater.
|
137
|
+
'berater.lock.capacity',
|
138
|
+
limiter.capacity,
|
139
|
+
Hash,
|
140
|
+
)
|
141
|
+
|
142
|
+
expect(client).to receive(:gauge).with(
|
143
|
+
'berater.lock.contention',
|
111
144
|
1,
|
112
145
|
Hash,
|
113
146
|
)
|
@@ -117,13 +150,13 @@ describe Berater::Middleware::Statsd do
|
|
117
150
|
|
118
151
|
it 'tracks each call' do
|
119
152
|
expect(client).to receive(:gauge).with(
|
120
|
-
'berater.
|
153
|
+
'berater.lock.contention',
|
121
154
|
1,
|
122
155
|
Hash,
|
123
156
|
)
|
124
157
|
|
125
158
|
expect(client).to receive(:gauge).with(
|
126
|
-
'berater.
|
159
|
+
'berater.lock.contention',
|
127
160
|
2,
|
128
161
|
Hash,
|
129
162
|
)
|
@@ -155,6 +188,7 @@ describe Berater::Middleware::Statsd do
|
|
155
188
|
it 'does not track the exception' do
|
156
189
|
expect(client).not_to receive(:increment).with(
|
157
190
|
'berater.limiter.error',
|
191
|
+
anything,
|
158
192
|
)
|
159
193
|
|
160
194
|
limiter.limit
|
@@ -162,11 +196,8 @@ describe Berater::Middleware::Statsd do
|
|
162
196
|
|
163
197
|
it 'does not track lock-based stats' do
|
164
198
|
expect(client).not_to receive(:gauge).with(
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
expect(client).not_to receive(:gauge).with(
|
169
|
-
'berater.limiter.contention',
|
199
|
+
/berater.lock/,
|
200
|
+
any_args,
|
170
201
|
)
|
171
202
|
|
172
203
|
limiter.limit
|
@@ -189,11 +220,8 @@ describe Berater::Middleware::Statsd do
|
|
189
220
|
|
190
221
|
it 'does not track lock-based stats' do
|
191
222
|
expect(client).not_to receive(:gauge).with(
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
expect(client).not_to receive(:gauge).with(
|
196
|
-
'berater.limiter.contention',
|
223
|
+
/berater.lock/,
|
224
|
+
any_args,
|
197
225
|
)
|
198
226
|
|
199
227
|
limiter.limit
|
@@ -215,16 +243,17 @@ describe Berater::Middleware::Statsd do
|
|
215
243
|
)
|
216
244
|
end
|
217
245
|
|
218
|
-
it '
|
246
|
+
it 'does not track lock-based stats' do
|
219
247
|
expect(client).not_to receive(:gauge).with(
|
220
|
-
|
221
|
-
|
248
|
+
/berater.lock/,
|
249
|
+
any_args,
|
222
250
|
)
|
223
251
|
end
|
224
252
|
|
225
253
|
it 'does not track the exception' do
|
226
254
|
expect(client).not_to receive(:increment).with(
|
227
255
|
'berater.limiter.error',
|
256
|
+
anything,
|
228
257
|
)
|
229
258
|
end
|
230
259
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'ddtrace'
|
2
|
+
|
3
|
+
describe Berater::Middleware::Trace do
|
4
|
+
before do
|
5
|
+
allow(tracer).to receive(:trace).and_yield(span)
|
6
|
+
end
|
7
|
+
|
8
|
+
it_behaves_like 'a limiter middleware'
|
9
|
+
|
10
|
+
let(:limiter) { Berater::Unlimiter.new }
|
11
|
+
|
12
|
+
if defined?(Datadog::Tracing)
|
13
|
+
before { Datadog.configure { |c| c.tracing.enabled = false } }
|
14
|
+
|
15
|
+
let(:span) { double(Datadog::Tracing::SpanOperation, set_tag: nil) }
|
16
|
+
let(:tracer) { double(Datadog::Tracing::Tracer) }
|
17
|
+
let(:ddtracer) { Datadog::Tracing }
|
18
|
+
else
|
19
|
+
before { Datadog.tracer.enabled = false }
|
20
|
+
|
21
|
+
let(:span) { double(Datadog::Span, set_tag: nil) }
|
22
|
+
let(:tracer) { double(Datadog::Tracer) }
|
23
|
+
let(:ddtracer) { Datadog.tracer }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#tracer' do
|
27
|
+
subject { instance.send(:tracer) }
|
28
|
+
|
29
|
+
let(:instance) { described_class.new }
|
30
|
+
|
31
|
+
it 'defaults to Datadog.tracer' do
|
32
|
+
is_expected.to be ddtracer
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when provided a tracer' do
|
36
|
+
let(:instance) { described_class.new(tracer: tracer) }
|
37
|
+
|
38
|
+
it { is_expected.to be tracer }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#call' do
|
43
|
+
let(:instance) { described_class.new(tracer: tracer) }
|
44
|
+
|
45
|
+
it 'yields' do
|
46
|
+
expect {|b| instance.call(limiter, &b) }.to yield_control
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'when a Berater::Overloaded exception is raised' do
|
50
|
+
it 'tags the span as overloaded and raises' do
|
51
|
+
expect(span).to receive(:set_tag).with('overloaded', true)
|
52
|
+
|
53
|
+
expect {
|
54
|
+
instance.call(limiter) { raise Berater::Overloaded }
|
55
|
+
}.to be_overloaded
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when an exception is raised' do
|
60
|
+
it 'tags the span and raises' do
|
61
|
+
expect(span).to receive(:set_tag).with('error', 'IOError')
|
62
|
+
|
63
|
+
expect {
|
64
|
+
instance.call(limiter) { raise IOError }
|
65
|
+
}.to raise_error(IOError)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'when used as middleware' do
|
71
|
+
before do
|
72
|
+
Berater.middleware.use described_class, tracer: tracer
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'traces' do
|
76
|
+
expect(tracer).to receive(:trace).with('Berater')
|
77
|
+
expect(span).to receive(:set_tag).with('key', limiter.key)
|
78
|
+
expect(span).to receive(:set_tag).with('capacity', Numeric)
|
79
|
+
expect(span).to receive(:set_tag).with('contention', Numeric)
|
80
|
+
|
81
|
+
limiter.limit
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when limiter is overloaded' do
|
85
|
+
let(:limiter) { Berater::Inhibitor.new }
|
86
|
+
|
87
|
+
it 'tags the span as overloaded and raises' do
|
88
|
+
expect(span).to receive(:set_tag).with('overloaded', true)
|
89
|
+
|
90
|
+
expect {
|
91
|
+
limiter.limit
|
92
|
+
}.to be_overloaded
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
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.1
|
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-07-13 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
|