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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d3ef54656151bf378670caca07686db842a007e87e51d14195cc3f2b8d34d34
4
- data.tar.gz: 22c6b65158ba926e78cd013c54913024c617eb5c2932297f7e4e424a72f66a14
3
+ metadata.gz: 1270edae5d2c019ae92b623ff4c26e588cf8d5413c8f7b6c549d3d3ec4e16766
4
+ data.tar.gz: a9a659acde5a04263b1ca516a2fb1ecdd760d9133f3603b4fa7ec336c06f59df
5
5
  SHA512:
6
- metadata.gz: 2743af38d4daf9cd5d18a06368834545add59fb6d192fdf17c7f503ab059961177c444e5b09efde6a8ed6739b4cca04d83e7e88f8d904d0bd3e2b1bc94220cfb
7
- data.tar.gz: a6750a44bcd05a1d0b25ff770d3c1d0f6fd3fd331cfebebbecdebd4ef06bc4f5a34d350cc36414cce7b212e7e63f8bb1d10cc4d25788ae50e1b973dfd280be4e
6
+ metadata.gz: 07fc3784c2e6d41648f97878e3a0c62e3a089c1ef69315e88141642350180634c65a89deda082ed5933a90666c620111329756d0699d3fde5238b97d0ad37e82
7
+ data.tar.gz: 6f8dc42b5b694a45c286b1d66a0b8efbf17371b3fd6bfd8936098b4155b85ae6523146fecfc6b7c46e3b0a8df1ddb03158f1fd181c9e6a1e13042232089e1a41
@@ -63,7 +63,7 @@ module Berater
63
63
  LUA
64
64
  )
65
65
 
66
- protected def acquire_lock(capacity, cost)
66
+ protected def acquire_lock(capacity:, cost:)
67
67
  # round fractional capacity and cost
68
68
  capacity = capacity.to_i
69
69
  cost = cost.ceil
@@ -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] ||= 1
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, cost)
114
+ def acquire_lock(capacity:, cost:)
114
115
  raise NotImplementedError
115
116
  end
116
117
 
117
- def cache_key(subkey = nil)
118
- instance_key = subkey.nil? ? key : "#{key}:#{subkey}"
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
- unless PRIORITY_RANGE.include?(priority)
27
- return capacity
28
- end
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
- # priority 1 stays at 100%, 2 scales down to 90%, 5 to 60%
31
- factor = 1 - (priority - 1) * 0.1
32
- (capacity * factor).floor
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
- # note exception and propagate
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 > 0 # not a failsafe lock
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.limiter.contention',
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.respond_to?(:call)
85
- tags.merge!(@tags.call(limiter, **opts) || {})
86
- else
87
- tags.merge!(@tags)
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.merge!(opts.fetch(: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
@@ -3,5 +3,6 @@ module Berater
3
3
  autoload 'FailOpen', 'berater/middleware/fail_open'
4
4
  autoload 'LoadShedder', 'berater/middleware/load_shedder'
5
5
  autoload 'Statsd', 'berater/middleware/statsd'
6
+ autoload 'Trace', 'berater/middleware/trace'
6
7
  end
7
8
  end
@@ -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, cost)
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
 
@@ -18,7 +18,7 @@ module Berater
18
18
  LUA
19
19
  )
20
20
 
21
- protected def acquire_lock(capacity, cost)
21
+ protected def acquire_lock(capacity:, cost:)
22
22
  if cost == 0
23
23
  # utilization check
24
24
  count = redis.get(cache_key) || "0"
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Berater
2
- VERSION = "0.12.1"
2
+ VERSION = "0.13.1"
3
3
  end
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).and_call_original
84
+ expect(limiter).to receive(:limit)
85
+
87
86
  subject
88
87
  end
89
88
 
90
89
  it 'yields' do
91
- is_expected.to be 123
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(3, anything)
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(3.5, anything)
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 stored value' do
70
- is_expected.to receive(:acquire_lock).with(anything, 2)
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(anything, 2.5)
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
- MyLimiter = Class.new(Berater::Unlimiter)
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:MyLimiter:key'
246
+ is_expected.to eq 'Berater:MyOtherLimiter:key'
224
247
  end
225
248
  end
226
249
  end
@@ -1,4 +1,6 @@
1
1
  describe Berater::Middleware::FailOpen do
2
+ it_behaves_like 'a limiter middleware'
3
+
2
4
  let(:limiter) { Berater::Unlimiter.new }
3
5
  let(:lock) { limiter.limit }
4
6
  let(:error) { Redis::TimeoutError }
@@ -1,4 +1,6 @@
1
1
  describe Berater::Middleware::LoadShedder do
2
+ it_behaves_like 'a limiter middleware'
3
+
2
4
  describe '#call' do
3
5
  subject { described_class.new }
4
6
 
@@ -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 { described_class.new(client, **client_opts).call(limiter, **opts, &block) }
21
+ subject do
22
+ described_class.new(client, **middleware_opts).call(limiter, **opts, &block)
23
+ end
14
24
 
15
- let(:client_opts) { {} }
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: lock.capacity, cost: 1 } }
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
- context 'when custom tags are passed in' do
54
- let(:opts) { { tags: { abc: 123 } } }
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
- it 'incorporates the tags' do
79
+ describe 'tags' do
80
+ def expect_tags_to(matcher)
57
81
  expect(client).to receive(:timing) do |*, tags:|
58
- expect(tags).to include(opts[:tags])
82
+ expect(tags).to matcher
59
83
  end
60
84
  end
61
- end
62
85
 
63
- context 'with global tags' do
64
- let(:client_opts) { { tags: { abc: 123 } }}
86
+ context 'with global tags' do
87
+ let(:middleware_opts) { { tags: { abc: 123 } }}
65
88
 
66
- it 'incorporates the tags' do
67
- expect(client).to receive(:timing) do |*, tags:|
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
- context 'with global tag callback' do
74
- let(:client_opts) { { tags: callback }}
75
- let(:callback) { double(Proc) }
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
- it 'calls the callback' do
78
- expect(callback).to receive(:call).with(limiter, **opts)
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
- it 'incorporates the tags' do
82
- expect(callback).to receive(:call).and_return({ abc: 123 })
108
+ context 'when call specific custom tags are passed in' do
109
+ let(:opts) { { tags: { abc: 123 } } }
83
110
 
84
- expect(client).to receive(:timing) do |*, tags:|
85
- expect(tags).to include(abc: 123)
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.limiter.contention',
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.limiter.contention',
153
+ 'berater.lock.contention',
121
154
  1,
122
155
  Hash,
123
156
  )
124
157
 
125
158
  expect(client).to receive(:gauge).with(
126
- 'berater.limiter.contention',
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
- 'berater.lock.capacity',
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
- 'berater.lock.capacity',
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 'tracks contention' do
246
+ it 'does not track lock-based stats' do
219
247
  expect(client).not_to receive(:gauge).with(
220
- 'berater.limiter.contention',
221
- limiter.capacity,
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
@@ -9,8 +9,13 @@ describe Berater::TestMode do
9
9
  end
10
10
 
11
11
  it 'prepends Limiter subclasses' do
12
- expect(Berater::Unlimiter.ancestors).to include(Berater::Limiter::TestMode)
13
- expect(Berater::Inhibitor.ancestors).to include(Berater::Limiter::TestMode)
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
- Berater::Unlimiter.new.limit
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
- it_behaves_like 'it supports test_mode'
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
- it_behaves_like 'it supports test_mode'
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.12.1
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: 2021-11-05 00:00:00.000000000 Z
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