expeditor 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 06fd643997ffcbbcf94b5243a56f00a31e4788a3
4
- data.tar.gz: 6aa12d927eab977c0d0b6c822b78b37877555637
3
+ metadata.gz: '0295f51ed41290a443f84e29704254cb92773e54'
4
+ data.tar.gz: d4525b17a023e7aed723ed8b5d40cc7842d14d6a
5
5
  SHA512:
6
- metadata.gz: f81815f7920543475b352185f62dca303173988600a16db97b329662f72d6405e14b7efd834b3ff2eb7d393e9eccf3daa4a8eb4072d36997c33cdca2e1f16ea0
7
- data.tar.gz: 1623ce5ccf9f6537e6ce87f0b056742dec4d71157c9108aedefbf4fd2924fa41211c50c76d13f8e9bedaa15d25a1192af2fe5d1f2994d2ad4f33cad7719788f6
6
+ metadata.gz: 5c07b956a48818d56ae0edfc9f282c21ffa7e90ba3432b91d3abba8a095456281cdc6ce22a8a887e72c096b94525a12fa9ddf72929761644df05492f70d02ea7
7
+ data.tar.gz: 047f91f6875c87e815796801f5a67e49b4d1bcb1821a7c36c8e4835ae6c1cd0a0cfc7992d69a079b1afeb6ec9ad8e6ddf4dcbbfb1b06bbfe97476bc42dcafcf3
data/.gitignore CHANGED
@@ -8,3 +8,4 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  /vendor/
11
+ .ruby-version
@@ -1,3 +1,10 @@
1
+ ## 0.7.0
2
+ - Add `gem 'concurrent-ruby-ext'` to your Gemfile if you want to use that gem.
3
+ - We should not depend on this in a gemspec [#27](https://github.com/cookpad/expeditor/pull/27)
4
+ - Fix possible race conditions.
5
+ - Fix bug on cutting passing size [#30](https://github.com/cookpad/expeditor/pull/30)
6
+ - Implement sleep feature on circuit breaker [#36](https://github.com/cookpad/expeditor/pull/36)
7
+
1
8
  ## 0.6.0
2
9
  - Improve default configuration of circuit breaker [#25](https://github.com/cookpad/expeditor/pull/25)
3
10
  - Default `non_break_count` is reduced from 100 to 20
data/Rakefile CHANGED
@@ -2,4 +2,9 @@ require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
- task default: :spec
5
+ task default: [:spec, :performance_test]
6
+
7
+ desc 'Check performance'
8
+ task :performance_test do
9
+ ruby 'scripts/command_performance.rb'
10
+ end
@@ -21,11 +21,12 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.required_ruby_version = '>= 2.1.0'
23
23
 
24
- spec.add_runtime_dependency "concurrent-ruby", "~> 1.0.0"
25
- spec.add_runtime_dependency "concurrent-ruby-ext", "~> 1.0.0"
24
+ spec.add_runtime_dependency "concurrent-ruby", ">= 1.0.0"
26
25
  spec.add_runtime_dependency "retryable", "> 1.0"
27
26
 
27
+ spec.add_development_dependency "benchmark-ips"
28
28
  spec.add_development_dependency "bundler"
29
+ spec.add_development_dependency "concurrent-ruby-ext", ">= 1.0.0"
29
30
  spec.add_development_dependency "rake"
30
31
  spec.add_development_dependency "rspec", ">= 3.0.0"
31
32
  end
@@ -1,4 +1,4 @@
1
- require 'expeditor/bucket'
1
+ require 'expeditor/rolling_number'
2
2
  require 'expeditor/command'
3
3
  require 'expeditor/errors'
4
4
  require 'expeditor/rich_future'
@@ -109,6 +109,9 @@ module Expeditor
109
109
  end
110
110
  end
111
111
 
112
+ # XXX: Raise ArgumentError when given `opts` has :dependencies
113
+ # because this forcefully change given :dependencies.
114
+ #
112
115
  # `chain` returns new command that has self as dependencies
113
116
  def chain(opts = {}, &block)
114
117
  opts[:dependencies] = [self]
@@ -125,59 +128,37 @@ module Expeditor
125
128
 
126
129
  private
127
130
 
128
- def reset_fallback(&block)
129
- @fallback_block = block
130
- end
131
-
132
- def breakable_block(args, &block)
133
- if @service.open?
134
- raise CircuitBreakError
135
- else
136
- block.call(*args)
137
- end
138
- end
139
-
140
- def retryable_block(args, &block)
141
- if @retryable_options.fulfilled?
142
- Retryable.retryable(@retryable_options.value) do |retries, exception|
143
- metrics(exception) if retries > 0
144
- breakable_block(args, &block)
145
- end
146
- else
147
- breakable_block(args, &block)
148
- end
149
- end
150
-
151
- def timeout_block(args, &block)
152
- if @timeout
153
- Timeout::timeout(@timeout) do
154
- retryable_block(args, &block)
131
+ # set future
132
+ # set fallback future as an observer
133
+ # start dependencies
134
+ def prepare(executor = @service.executor)
135
+ @normal_future = initial_normal(executor, &@normal_block)
136
+ @normal_future.add_observer do |_, value, reason|
137
+ if reason # failure
138
+ if @fallback_block
139
+ future = RichFuture.new(executor: executor) do
140
+ success, value, reason = Concurrent::SafeTaskExecutor.new(@fallback_block, rescue_exception: true).execute(reason)
141
+ if success
142
+ @ivar.set(value)
143
+ else
144
+ @ivar.fail(reason)
145
+ end
146
+ end
147
+ future.safe_execute
148
+ else
149
+ @ivar.fail(reason)
150
+ end
151
+ else # success
152
+ @ivar.set(value)
155
153
  end
156
- else
157
- retryable_block(args, &block)
158
154
  end
159
- end
160
155
 
161
- def metrics(reason)
162
- case reason
163
- when nil
164
- @service.success
165
- when Timeout::Error
166
- @service.timeout
167
- when RejectedExecutionError
168
- @service.rejection
169
- when CircuitBreakError
170
- @service.break
171
- when DependencyError
172
- @service.dependency
173
- else
174
- @service.failure
175
- end
156
+ @dependencies.each(&:start)
176
157
  end
177
158
 
178
- # timeout do
179
- # retryable do
180
- # circuit break do
159
+ # timeout_block do
160
+ # retryable_block do
161
+ # breakable_block do
181
162
  # block.call
182
163
  # end
183
164
  # end
@@ -224,36 +205,56 @@ module Expeditor
224
205
  end
225
206
  end
226
207
 
227
- def on(&callback)
228
- @ivar.add_observer(&callback)
208
+ def timeout_block(args, &block)
209
+ if @timeout
210
+ Timeout::timeout(@timeout) do
211
+ retryable_block(args, &block)
212
+ end
213
+ else
214
+ retryable_block(args, &block)
215
+ end
229
216
  end
230
217
 
231
- # set future
232
- # set fallback future as an observer
233
- # start dependencies
234
- def prepare(executor = @service.executor)
235
- @normal_future = initial_normal(executor, &@normal_block)
236
- @normal_future.add_observer do |_, value, reason|
237
- if reason # failure
238
- if @fallback_block
239
- future = RichFuture.new(executor: executor) do
240
- success, value, reason = Concurrent::SafeTaskExecutor.new(@fallback_block, rescue_exception: true).execute(reason)
241
- if success
242
- @ivar.set(value)
243
- else
244
- @ivar.fail(reason)
245
- end
246
- end
247
- future.safe_execute
248
- else
249
- @ivar.fail(reason)
250
- end
251
- else # success
252
- @ivar.set(value)
218
+ def retryable_block(args, &block)
219
+ if @retryable_options.fulfilled?
220
+ Retryable.retryable(@retryable_options.value) do |retries, exception|
221
+ metrics(exception) if retries > 0
222
+ breakable_block(args, &block)
253
223
  end
224
+ else
225
+ breakable_block(args, &block)
254
226
  end
227
+ end
255
228
 
256
- @dependencies.each(&:start)
229
+ def breakable_block(args, &block)
230
+ @service.run_if_allowed do
231
+ block.call(*args)
232
+ end
233
+ end
234
+
235
+ def metrics(reason)
236
+ case reason
237
+ when nil
238
+ @service.success
239
+ when Timeout::Error
240
+ @service.timeout
241
+ when RejectedExecutionError
242
+ @service.rejection
243
+ when CircuitBreakError
244
+ @service.break
245
+ when DependencyError
246
+ @service.dependency
247
+ else
248
+ @service.failure
249
+ end
250
+ end
251
+
252
+ def reset_fallback(&block)
253
+ @fallback_block = block
254
+ end
255
+
256
+ def on(&callback)
257
+ @ivar.add_observer(&callback)
257
258
  end
258
259
 
259
260
  class ConstCommand < Command
@@ -0,0 +1,76 @@
1
+ module Expeditor
2
+ # Circular buffer with user-defined initialization and optimized `move`
3
+ # implementation. The next element will be always initialized with
4
+ # user-defined initialization proc.
5
+ #
6
+ # Thread unsafe.
7
+ class RingBuffer
8
+ # @params [Integer] size
9
+ def initialize(size, &initialize_proc)
10
+ raise ArgumentError.new('initialize_proc is not given') unless initialize_proc
11
+ @size = size
12
+ @initialize_proc = initialize_proc
13
+ @elements = Array.new(@size, &initialize_proc)
14
+ @current_index = 0
15
+ end
16
+
17
+ # @return [Object] user created object with given initialization proc.
18
+ def current
19
+ @elements[@current_index]
20
+ end
21
+
22
+ # @params [Integer] times How many elements will we pass.
23
+ # @return [Object] current element after moving.
24
+ def move(times)
25
+ cut_moving_time_if_possible(times).times do
26
+ next_element
27
+ end
28
+ end
29
+
30
+ # @return [Array<Object>] Array of elements.
31
+ def all
32
+ @elements
33
+ end
34
+
35
+ private
36
+
37
+ # This logic is used for cutting moving times. When moving times is greater
38
+ # than statuses size, we can cut moving times to less than statuses size
39
+ # because the statuses are circulated.
40
+ #
41
+ # `*` is current index.
42
+ # When the statuses size is 3:
43
+ #
44
+ # [*, , ]
45
+ #
46
+ # Then when the moving times = 3, current index will be 0 (0-origin):
47
+ #
48
+ # [*, , ] -3> [ ,*, ] -2> [ , ,*] -1> [*, , ]
49
+ #
50
+ # Then moving times = 6, current index will be 0 again:
51
+ #
52
+ # [*, , ] -6> [ ,*, ] -5> [ , ,*] -4> [*, , ] -3> [ ,*, ] -2> [ , ,*] -1> [*, , ]
53
+ #
54
+ # In that case we can cut the moving times from 6 to 3.
55
+ # That is "cut moving times" here.
56
+ #
57
+ # TODO: We can write more optimized code which resets all elements with
58
+ # Array.new if given moving times is greater than `@size`.
59
+ def cut_moving_time_if_possible(times)
60
+ if times >= @size * 2
61
+ (times % @size) + @size
62
+ else
63
+ times
64
+ end
65
+ end
66
+
67
+ def next_element
68
+ if @current_index == @size - 1
69
+ @current_index = 0
70
+ else
71
+ @current_index += 1
72
+ end
73
+ @elements[@current_index] = @initialize_proc.call
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,58 @@
1
+ require 'expeditor/status'
2
+ require 'expeditor/ring_buffer'
3
+
4
+ module Expeditor
5
+ # A RollingNumber holds some Status objects and it rolls statuses each
6
+ # `per_time` (default is 1 second). This is done so that the statistics are
7
+ # recorded gradually with short time interval rahter than reset all the
8
+ # record every wide time range (default is 10 seconds).
9
+ class RollingNumber
10
+ def initialize(size:, per_time:)
11
+ @mutex = Mutex.new
12
+ @ring = RingBuffer.new(size) do
13
+ Expeditor::Status.new
14
+ end
15
+ @per_time = per_time
16
+ @current_start = Time.now
17
+ end
18
+
19
+ # @params [Symbol] type
20
+ def increment(type)
21
+ @mutex.synchronize do
22
+ update
23
+ @ring.current.increment(type)
24
+ end
25
+ end
26
+
27
+ # @return [Expeditor::Status] Newly created status
28
+ def total
29
+ @mutex.synchronize do
30
+ update
31
+ @ring.all.inject(Expeditor::Status.new) {|i, s| i.merge!(s) }
32
+ end
33
+ end
34
+
35
+ # @deprecated Don't use, use `#total` instead.
36
+ def current
37
+ warn 'Expeditor::RollingNumber#current is deprecated. Please use #total instead to fetch correct status object.'
38
+ @mutex.synchronize do
39
+ update
40
+ @ring.current
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def update
47
+ passing = last_passing
48
+ if passing > 0
49
+ @current_start = @current_start + @per_time * passing
50
+ @ring.move(passing)
51
+ end
52
+ end
53
+
54
+ def last_passing
55
+ (Time.now - @current_start).div(@per_time)
56
+ end
57
+ end
58
+ end
@@ -6,62 +6,88 @@ module Expeditor
6
6
  attr_accessor :fallback_enabled
7
7
 
8
8
  def initialize(opts = {})
9
+ @mutex = Mutex.new
9
10
  @executor = opts.fetch(:executor) { Concurrent::ThreadPoolExecutor.new }
10
11
  @threshold = opts.fetch(:threshold, 0.5)
11
12
  @non_break_count = opts.fetch(:non_break_count, 20)
12
13
  @sleep = opts.fetch(:sleep, 1)
13
- @bucket_opts = {
14
- size: 10,
15
- per: opts.fetch(:period, 10).to_f / 10
14
+ granularity = 10
15
+ @rolling_number_opts = {
16
+ size: granularity,
17
+ per_time: opts.fetch(:period, 10).to_f / granularity
16
18
  }
17
19
  reset_status!
18
20
  @fallback_enabled = true
19
21
  end
20
22
 
21
23
  def success
22
- @bucket.increment :success
24
+ @rolling_number.increment :success
23
25
  end
24
26
 
25
27
  def failure
26
- @bucket.increment :failure
28
+ @rolling_number.increment :failure
27
29
  end
28
30
 
29
31
  def rejection
30
- @bucket.increment :rejection
32
+ @rolling_number.increment :rejection
31
33
  end
32
34
 
33
35
  def timeout
34
- @bucket.increment :timeout
36
+ @rolling_number.increment :timeout
35
37
  end
36
38
 
37
39
  def break
38
- @bucket.increment :break
40
+ @rolling_number.increment :break
39
41
  end
40
42
 
41
43
  def dependency
42
- @bucket.increment :dependency
44
+ @rolling_number.increment :dependency
43
45
  end
44
46
 
45
47
  def fallback_enabled?
46
48
  !!fallback_enabled
47
49
  end
48
50
 
49
- # break circuit?
50
- def open?
51
+ def breaking?
52
+ @breaking
53
+ end
54
+
55
+ # Run given block when the request is allowed, otherwise raise
56
+ # Expeditor::CircuitBreakError. When breaking and sleep time was passed,
57
+ # the circuit breaker tries to close the circuit. So subsequent single
58
+ # command execution is allowed (will not be breaked) to check the service
59
+ # is healthy or not. The circuit breaker only allows one request so other
60
+ # subsequent requests will be aborted with CircuitBreakError. When the test
61
+ # request succeeds, the circuit breaker resets the service status and
62
+ # closes the circuit.
63
+ def run_if_allowed
51
64
  if @breaking
52
- if Time.now - @break_start > @sleep
53
- @breaking = false
54
- @break_start = nil
65
+ now = Time.now
66
+
67
+ # Only one thread can be allowed to execute single request when half-opened.
68
+ allow_single_request = false
69
+ @mutex.synchronize do
70
+ allow_single_request = now - @break_start > @sleep
71
+ @break_start = now if allow_single_request
72
+ end
73
+
74
+ if allow_single_request
75
+ result = yield # This can be raise exception.
76
+ # The execution succeed, then
77
+ reset_status!
78
+ result
55
79
  else
56
- return true
80
+ raise CircuitBreakError
81
+ end
82
+ else
83
+ open = calc_open
84
+ if open
85
+ change_state(true, Time.now)
86
+ raise CircuitBreakError
87
+ else
88
+ yield
57
89
  end
58
90
  end
59
- open = calc_open
60
- if open
61
- @breaking = true
62
- @break_start = Time.now
63
- end
64
- open
65
91
  end
66
92
 
67
93
  # shutdown thread pool
@@ -71,25 +97,27 @@ module Expeditor
71
97
  end
72
98
 
73
99
  def status
74
- @bucket.total
100
+ @rolling_number.total
75
101
  end
76
102
 
77
- # @deprecated
103
+ # @deprecated Use `#status` instead.
78
104
  def current_status
79
- warn 'Expeditor::Service#current_status is deprecated. Please use #status instead'
80
- @bucket.current
105
+ warn 'Expeditor::Service#current_status is deprecated. Please use #status instead.'
106
+ @rolling_number.current
81
107
  end
82
108
 
83
109
  def reset_status!
84
- @bucket = Expeditor::Bucket.new(@bucket_opts)
85
- @breaking = false
86
- @break_start = nil
110
+ @mutex.synchronize do
111
+ @rolling_number = Expeditor::RollingNumber.new(@rolling_number_opts)
112
+ @breaking = false
113
+ @break_start = nil
114
+ end
87
115
  end
88
116
 
89
117
  private
90
118
 
91
119
  def calc_open
92
- s = @bucket.total
120
+ s = @rolling_number.total
93
121
  total_count = s.success + s.failure + s.timeout
94
122
  if total_count >= [@non_break_count, 1].max
95
123
  failure_count = s.failure + s.timeout
@@ -98,5 +126,12 @@ module Expeditor
98
126
  false
99
127
  end
100
128
  end
129
+
130
+ def change_state(breaking, break_start)
131
+ @mutex.synchronize do
132
+ @breaking = breaking
133
+ @break_start = break_start
134
+ end
135
+ end
101
136
  end
102
137
  end
@@ -28,8 +28,8 @@ module Expeditor
28
28
  def dependency
29
29
  end
30
30
 
31
- def open?
32
- false
31
+ def run_if_allowed
32
+ yield
33
33
  end
34
34
  end
35
35
  end
@@ -1,34 +1,46 @@
1
1
  module Expeditor
2
+ # Thread unsafe.
2
3
  class Status
3
- attr_accessor :success
4
- attr_accessor :failure
5
- attr_accessor :rejection
6
- attr_accessor :timeout
7
- attr_accessor :break
8
- attr_accessor :dependency
4
+ attr_reader :success
5
+ attr_reader :failure
6
+ attr_reader :rejection
7
+ attr_reader :timeout
8
+ attr_reader :break
9
+ attr_reader :dependency
9
10
 
10
11
  def initialize
11
12
  set(0, 0, 0, 0, 0, 0)
12
13
  end
13
14
 
14
- def increment(type)
15
+ def increment(type, i = 1)
15
16
  case type
16
17
  when :success
17
- @success += 1
18
+ @success += i
18
19
  when :failure
19
- @failure += 1
20
+ @failure += i
20
21
  when :rejection
21
- @rejection += 1
22
+ @rejection += i
22
23
  when :timeout
23
- @timeout += 1
24
+ @timeout += i
24
25
  when :break
25
- @break += 1
26
+ @break += i
26
27
  when :dependency
27
- @dependency += 1
28
+ @dependency += i
28
29
  else
30
+ raise ArgumentError.new("Unknown type: #{type}")
29
31
  end
30
32
  end
31
33
 
34
+ def merge!(other)
35
+ increment(:success, other.success)
36
+ increment(:failure, other.failure)
37
+ increment(:rejection, other.rejection)
38
+ increment(:timeout, other.timeout)
39
+ increment(:break, other.break)
40
+ increment(:dependency, other.dependency)
41
+ self
42
+ end
43
+
32
44
  def reset
33
45
  set(0, 0, 0, 0, 0, 0)
34
46
  end
@@ -1,3 +1,3 @@
1
1
  module Expeditor
2
- VERSION = "0.6.0"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -0,0 +1,25 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
2
+ require 'expeditor'
3
+
4
+ require 'benchmark/ips'
5
+
6
+ Benchmark.ips do |x|
7
+ x.report("simple command") do |i|
8
+ executor = Concurrent::ThreadPoolExecutor.new(min_threads: 100, max_threads: 100, max_queue: 100)
9
+ service = Expeditor::Service.new(period: 10, non_break_count: 0, threshold: 0.5, sleep: 1, executor: executor)
10
+
11
+ i.times do
12
+ commands = 10000.times.map do
13
+ Expeditor::Command.new { 1 }.start
14
+ end
15
+ command = Expeditor::Command.new(service: service, dependencies: commands) do |*vs|
16
+ vs.inject(0, &:+)
17
+ end.start
18
+ command.get
19
+
20
+ service.reset_status!
21
+ end
22
+ end
23
+
24
+ x.compare!
25
+ end
metadata CHANGED
@@ -1,57 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expeditor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - shohei-yasutake
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-04-24 00:00:00.000000000 Z
11
+ date: 2017-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 1.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.0.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: concurrent-ruby-ext
28
+ name: retryable
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.0.0
33
+ version: '1.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.0.0
40
+ version: '1.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: retryable
42
+ name: benchmark-ips
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '1.0'
48
- type: :runtime
47
+ version: '0'
48
+ type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '1.0'
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: concurrent-ruby-ext
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 1.0.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.0
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rake
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -115,15 +129,17 @@ files:
115
129
  - examples/example.rb
116
130
  - expeditor.gemspec
117
131
  - lib/expeditor.rb
118
- - lib/expeditor/bucket.rb
119
132
  - lib/expeditor/command.rb
120
133
  - lib/expeditor/errors.rb
121
134
  - lib/expeditor/rich_future.rb
135
+ - lib/expeditor/ring_buffer.rb
136
+ - lib/expeditor/rolling_number.rb
122
137
  - lib/expeditor/service.rb
123
138
  - lib/expeditor/services.rb
124
139
  - lib/expeditor/services/default.rb
125
140
  - lib/expeditor/status.rb
126
141
  - lib/expeditor/version.rb
142
+ - scripts/command_performance.rb
127
143
  homepage: https://github.com/cookpad/expeditor
128
144
  licenses:
129
145
  - MIT
@@ -1,85 +0,0 @@
1
- require 'expeditor/status'
2
-
3
- module Expeditor
4
- # Bucket is a data structure like circular buffer. It holds some status
5
- # objects and it rolls statuses each `per` time (default is 1 second). Once
6
- # it reaches the end of statuses array, it backs to start of statuses array
7
- # and then reset the status and resumes recording. This is done so that the
8
- # statistics are recorded gradually with short time interval rahter than
9
- # reset all the record every wide time range (default is 10 seconds).
10
- class Bucket
11
- def initialize(opts = {})
12
- @mutex = Mutex.new
13
- @current_index = 0
14
- @size = opts.fetch(:size, 10)
15
- @per_time = opts.fetch(:per, 1)
16
- @current_start = Time.now
17
- @statuses = [].fill(0..(@size - 1)) do
18
- Expeditor::Status.new
19
- end
20
- end
21
-
22
- def increment(type)
23
- @mutex.synchronize do
24
- update
25
- @statuses[@current_index].increment type
26
- end
27
- end
28
-
29
- def total
30
- acc = @mutex.synchronize do
31
- update
32
- @statuses.inject([0, 0, 0, 0, 0, 0]) do |i, s|
33
- i[0] += s.success
34
- i[1] += s.failure
35
- i[2] += s.rejection
36
- i[3] += s.timeout
37
- i[4] += s.break
38
- i[5] += s.dependency
39
- i
40
- end
41
- end
42
- status = Expeditor::Status.new
43
- status.success = acc[0]
44
- status.failure = acc[1]
45
- status.rejection = acc[2]
46
- status.timeout = acc[3]
47
- status.break = acc[4]
48
- status.dependency = acc[5]
49
- status
50
- end
51
-
52
- def current
53
- @mutex.synchronize do
54
- update
55
- @statuses[@current_index]
56
- end
57
- end
58
-
59
- private
60
-
61
- def update
62
- passing = last_passing
63
- if passing > 0
64
- @current_start = @current_start + @per_time * passing
65
- passing = passing.div @size + @size if passing > 2 * @size
66
- passing.times do
67
- @current_index = next_index
68
- @statuses[@current_index].reset
69
- end
70
- end
71
- end
72
-
73
- def last_passing
74
- (Time.now - @current_start).div @per_time
75
- end
76
-
77
- def next_index
78
- if @current_index == @size - 1
79
- 0
80
- else
81
- @current_index + 1
82
- end
83
- end
84
- end
85
- end