expeditor 0.6.0 → 0.7.0

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