berater 0.12.1 → 0.13.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|