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 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