rereplay 0.1 → 0.2

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.
@@ -0,0 +1,25 @@
1
+ infinity_test do
2
+
3
+ notifications :lib_notify do
4
+ show_images# :mode => :mario_bros
5
+ end
6
+
7
+ use :rubies => %w(1.8.7 1.9.2 ruby-head jruby), :test_framework => :rspec
8
+
9
+ before(:each_ruby) do |environment|
10
+ # ...
11
+ end
12
+
13
+ after(:each_ruby) do |environment|
14
+ # ...
15
+ end
16
+
17
+ before_run do
18
+ clear :terminal
19
+ end
20
+
21
+ after_run do
22
+ # ...
23
+ end
24
+
25
+ end
data/Gemfile CHANGED
@@ -1,13 +1,7 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem "eventmachine", "~>0.12.10"
4
- gem "em-http-request", "~>0.2.14"
5
-
6
- group :test do
7
- gem "rspec", "~>2.0"
8
- gem "webmock", "~>1.4.0"
9
- gem "activesupport", "~>3.0.1"
10
- # gem "autotest"
11
- # gem "test_notifier"
12
- end
3
+ gem "infinity_test", :git => "git://github.com/tomas-stefano/infinity_test.git"
4
+ gem "rspec", "~>2.0"
5
+ gem "webmock", "~>1.4.0"
6
+ gem "activesupport", "~>3.0.1"
13
7
 
@@ -1,3 +1,11 @@
1
+ GIT
2
+ remote: git://github.com/tomas-stefano/infinity_test.git
3
+ revision: c732c6bcc54e8745fba4dbd4565e068a2a329e1a
4
+ specs:
5
+ infinity_test (0.2.0)
6
+ notifiers (>= 1.1.0)
7
+ watchr (>= 0.7)
8
+
1
9
  GEM
2
10
  remote: http://rubygems.org/
3
11
  specs:
@@ -5,10 +13,7 @@ GEM
5
13
  addressable (2.2.2)
6
14
  crack (0.1.8)
7
15
  diff-lcs (1.1.2)
8
- em-http-request (0.2.14)
9
- addressable (>= 2.0.0)
10
- eventmachine (>= 0.12.9)
11
- eventmachine (0.12.10)
16
+ notifiers (1.1.0)
12
17
  rspec (2.0.1)
13
18
  rspec-core (~> 2.0.1)
14
19
  rspec-expectations (~> 2.0.1)
@@ -19,16 +24,17 @@ GEM
19
24
  rspec-mocks (2.0.1)
20
25
  rspec-core (~> 2.0.1)
21
26
  rspec-expectations (~> 2.0.1)
27
+ watchr (0.7)
22
28
  webmock (1.4.0)
23
29
  addressable (>= 2.2.2)
24
30
  crack (>= 0.1.7)
25
31
 
26
32
  PLATFORMS
33
+ java
27
34
  ruby
28
35
 
29
36
  DEPENDENCIES
30
37
  activesupport (~> 3.0.1)
31
- em-http-request (~> 0.2.14)
32
- eventmachine (~> 0.12.10)
38
+ infinity_test!
33
39
  rspec (~> 2.0)
34
40
  webmock (~> 1.4.0)
data/README.md CHANGED
@@ -14,17 +14,17 @@ There are a couple other main features as well. You can provide Request Monitor
14
14
  [0.5, :get, "http://www.microsoft.com/"],
15
15
  [0.9, :get, "http://www.amazon.com/"]
16
16
  ]
17
- r = ReReplay.new(input)
17
+ r = ReReplay::Runner.new(input)
18
18
  r.run
19
19
  # and done!
20
20
 
21
21
  Of course, this doesn't actually track any output, so...let's monitor the request time using the request_time_monitor:
22
22
 
23
23
  ## Request Monitor
24
- require "rereplay/monitors/request_time_monitor"
24
+ require "rereplay/monitors"
25
25
  input = [same as in Simple]
26
- mon = RequestTimeMonitor.new
27
- r = ReReplay.new(input)
26
+ mon = ReReplay::RequestTimeMonitor.new
27
+ r = ReReplay::Runner.new(input)
28
28
  r.request_monitors << mon
29
29
  r.run
30
30
  puts mon.results.inspect
@@ -1,5 +1 @@
1
- require 'ostruct'
2
- require 'eventmachine'
3
- require 'em-http'
4
-
5
- require 'rereplay/runner'
1
+ require 'rereplay/ruby_runner'
@@ -7,7 +7,7 @@ module ReReplay
7
7
  end
8
8
 
9
9
  def finish(request)
10
- @results[request.index] = [request.url, request.finish - request.actual_start, request.scheduled_start, request.actual_start]
10
+ @results[request.index] = {:url => request.url, :duration => request.finish - request.actual_start, :scheduled_start => request.scheduled_start, :actual_start => request.actual_start}
11
11
  end
12
12
  end
13
13
  end
@@ -1,5 +1,6 @@
1
1
  module ReReplay
2
2
  class TimeoutFailer
3
+ class Timeout < StandardError; end
3
4
  def initialize(max_timeouts=1)
4
5
  @max_timeouts = max_timeouts
5
6
  @timeouts = 0
@@ -10,7 +11,7 @@ module ReReplay
10
11
  @timeouts += 1
11
12
  end
12
13
  if(@timeouts >= @max_timeouts)
13
- raise "TimeoutFailer triggered because timeout limit #{@max_timeouts} was reached"
14
+ raise Timeout, "TimeoutFailer triggered because timeout limit #{@max_timeouts} was reached"
14
15
  end
15
16
  end
16
17
  end
@@ -0,0 +1,250 @@
1
+ require 'ostruct'
2
+ require 'thread'
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'monitor'
6
+
7
+ module ReReplay
8
+ class TestDurationExceeded < StandardError; end
9
+ class Runner
10
+
11
+ attr_accessor :periodic_monitors
12
+ attr_accessor :request_monitors
13
+
14
+ def initialize(input=nil)
15
+ if(!input.nil?)
16
+ self.input = input
17
+ end
18
+ @periodic_monitors = []
19
+ @request_monitors = []
20
+ end
21
+
22
+ def input=(input)
23
+ if(input.is_a? Array)
24
+ @input = input
25
+ elsif(input.respond_to? :readlines)
26
+ @input = input.readlines
27
+ elsif(input.respond_to? :split)
28
+ @input = input.split("\n").map do |i|
29
+ i = i.strip.split(",").map {|j| j.strip}
30
+ i[0] = i[0].to_f
31
+ i[1] = i[1].to_sym
32
+ i
33
+ end
34
+ else
35
+ raise "Invalid input, expected Array, #readlines, or #split"
36
+ end
37
+ end
38
+
39
+ def validate_input
40
+ if(@input.nil? || @input.empty?)
41
+ raise ArgumentError, "Nothing to process (input was empty)"
42
+ end
43
+ valid_methods = [:get, :head]
44
+ @input.each_with_index do |a, i|
45
+ if(!a[0].is_a? Numeric)
46
+ raise ArgumentError, "Expected element at index 0 of input #{i+1} to be Numeric; was #{a[0]}"
47
+ end
48
+ if(!a[1].is_a?(Symbol) || !valid_methods.include?(a[1]))
49
+ raise ArgumentError, "Expected element at index 1 of input #{i+1} to be a symbol in #{valid_methods.inspect}; was #{a[1].inspect}"
50
+ end
51
+ if(!a[2].is_a? String)
52
+ raise ArgumentError, "Expected element at index 2 of input #{i+1} to be a String; was #{a[2]}"
53
+ end
54
+ if(!a[3].nil? && !a[3].is_a?(Hash))
55
+ raise ArgumentError, "Expected element at index 3 of input #{i+1} to be nil or a Hash; was #{a[3]}"
56
+ end
57
+ # TODO post data
58
+ end
59
+ end
60
+
61
+ def prepare
62
+ p = profile
63
+
64
+ max_time = @input.max {|a, b| a[0] <=> b[0]}[0]
65
+
66
+ loop_count = 1
67
+ if(p[:when_input_consumed] == :loop)
68
+ if(max_time < p[:run_for])
69
+ loop_count = (p[:run_for].to_f / max_time).ceil
70
+ end
71
+ end
72
+ if(loop_count > 1)
73
+ new_inputs = []
74
+ 2.upto(loop_count) do |loop|
75
+ new_input = @input.map do |i|
76
+ new_i = i.dup
77
+ new_i[0] += max_time * (loop - 1)
78
+ new_i
79
+ end
80
+ new_inputs << new_input
81
+ end
82
+ new_inputs.each {|input| @input += input}
83
+ end
84
+ real_max_time = [max_time * loop_count, p[:run_for]].min
85
+ if(p[:rampup][0] != p[:rampup][1] || p[:rampup][0] != 1.0)
86
+ case p[:rampup_method]
87
+ when :linear
88
+ sr = 1.0 / p[:rampup][0]
89
+ fr = 1.0 / p[:rampup][1]
90
+ prev_time = 0
91
+ new_prev_time = 0
92
+ @input.map! do |a|
93
+ time = a[0].to_f
94
+ percent = time / real_max_time
95
+ fraction = sr + (fr - sr)*(time / real_max_time)
96
+ tmp = a[0]
97
+ a[0] = (time - prev_time)*fraction + new_prev_time
98
+ prev_time = tmp
99
+ new_prev_time = a[0]
100
+ a
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def run
107
+ validate_input
108
+ p = profile
109
+ done_count = 0
110
+ # request monitors with a start method
111
+ request_monitors_start = request_monitors.select {|mon| mon.respond_to? :start}
112
+ # request monitors with a finish method
113
+ request_monitors_finish = request_monitors.select {|mon| mon.respond_to? :finish}
114
+ tg = ThreadGroup.new
115
+ q = Queue.new
116
+ q.extend MonitorMixin
117
+ waiters_cond = q.new_cond
118
+
119
+ prepare
120
+ start_time = nil
121
+ index = 0
122
+ requests_to_make = @input.map do |r|
123
+ a = r.dup
124
+ a[3] = index
125
+ index += 1
126
+ a
127
+ end
128
+ thread_count = 20
129
+ done = 0
130
+ total_requests = @input.length
131
+ max_delay = 0
132
+ parent_thread = Thread.current
133
+ Thread.abort_on_exception = true
134
+ ready_for_processing = false
135
+ gatekeeper = Thread.new do
136
+ q.synchronize do
137
+ waiters_cond.wait_until { ready_for_processing }
138
+ end
139
+ until requests_to_make.empty?
140
+ task = requests_to_make.shift
141
+ since_start = Time.new - start_time
142
+ time_until_next_task = task[0] - since_start
143
+
144
+ if(time_until_next_task > 0)
145
+ sleep time_until_next_task
146
+ end
147
+
148
+ q << task
149
+ end
150
+ end
151
+
152
+ thread_count.times do
153
+ t = Thread.new do
154
+ while true
155
+ task = q.pop
156
+ now = Time.new
157
+ since_start = now - start_time
158
+ delay = since_start - task[0]
159
+ if(delay > max_delay) then max_delay = delay; end
160
+ url = URI.parse(task[2])
161
+ req = Net::HTTP::Get.new(url.path)
162
+ request = OpenStruct.new(:url => task[2], :scheduled_start => task[0], :index => task[3], :http_method => task[1])
163
+ # this connection can actually take ~300ms...is there a better way?
164
+ Net::HTTP.start(url.host, url.port) do |http|
165
+ http.read_timeout = p[:timeout]
166
+ status = nil
167
+ begin
168
+ #request.actual_start = Time.now - start_time
169
+ request.actual_start = now - start_time
170
+ resp = http.request(req)
171
+ request_monitors_start.each {|mon| mon.start(request)}
172
+ rescue Timeout::Error
173
+ status = :timeout
174
+ end
175
+ if status.nil?
176
+ status = resp.code
177
+ end
178
+ time_finished = Time.now - start_time
179
+ request.finish = time_finished
180
+ request.status = status
181
+ begin
182
+ request_monitors_finish.each {|mon| mon.finish(request)}
183
+ rescue => e
184
+ parent_thread.raise e
185
+ end
186
+ q.synchronize do
187
+ done += 1
188
+ waiters_cond.broadcast
189
+ end
190
+ end
191
+ end
192
+ end
193
+ tg.add t
194
+ end
195
+ test_duration_exceeded = false
196
+ q.synchronize do
197
+ ready_for_processing = true
198
+ start_time = Time.now
199
+ waiters_cond.broadcast
200
+ end
201
+ timeout_thread = Thread.new do
202
+ sleep_duration = start_time + p[:run_for] - Time.now
203
+ sleep sleep_duration
204
+ q.synchronize do
205
+ test_duration_exceeded = true
206
+ waiters_cond.broadcast
207
+ end
208
+ end
209
+ periodic_monitor_threads = []
210
+ periodic_monitors.each do |mon|
211
+ interval = mon.respond_to?(:interval) ? mon.interval : 5
212
+ periodic_monitor_threads << Thread.new do
213
+ i = 0
214
+ while true
215
+ mon.tick(Time.now - start_time)
216
+ i += 1
217
+ time_to_next = start_time + (interval * i) - Time.now
218
+ sleep time_to_next if time_to_next > 0
219
+ end
220
+ end
221
+ end
222
+ q.synchronize do
223
+ waiters_cond.wait_while { done < total_requests && !test_duration_exceeded }
224
+ end
225
+ ensure
226
+ gatekeeper.kill if gatekeeper
227
+ tg.list.each {|t| t.kill} if tg
228
+ periodic_monitor_threads.each {|t| t.kill} if periodic_monitor_threads
229
+ end
230
+
231
+ def profile=(new_profile)
232
+ @profile = {
233
+ :run_for => 5,
234
+ :when_input_consumed => :stop,
235
+ :timeout => 1,
236
+ :rampup => [1.0, 1.0],
237
+ :rampup_method => :linear
238
+ }
239
+ if(new_profile.is_a? Hash)
240
+ @profile.merge!(new_profile)
241
+ end
242
+ end
243
+ def profile
244
+ if(@profile.nil?)
245
+ self.profile = {}
246
+ end
247
+ @profile
248
+ end
249
+ end
250
+ end
@@ -1,3 +1,3 @@
1
1
  module ReReplay
2
- VERSION = "0.1"
2
+ VERSION = "0.2"
3
3
  end
@@ -15,9 +15,6 @@ Gem::Specification.new do |s|
15
15
 
16
16
  s.required_rubygems_version = ">= 1.3.6"
17
17
 
18
- s.add_runtime_dependency "eventmachine", ">= 0.12.10", "< 0.13"
19
- s.add_runtime_dependency "em-http-request", ">= 0.2.14", "< 0.3"
20
-
21
18
  s.add_development_dependency "rspec", ">= 2.0", "< 3"
22
19
  s.add_development_dependency "webmock", ">= 1.4.0", "< 1.5"
23
20
  s.add_development_dependency "active_support", ">= 3.0.1", "< 3.1"
@@ -1,4 +1,4 @@
1
- require 'spec/spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe ReReplay, "advanced functions" do
4
4
  it "should support linear rampup" do
@@ -9,7 +9,7 @@ describe ReReplay, "advanced functions" do
9
9
  :rampup => [1.0, 2.0]
10
10
  }
11
11
  r.profile = profile
12
- lambda { r.run }.should take_between(1.65.seconds).and(1.8.seconds)
12
+ lambda { r.run }.should take_between(0.65.seconds).and(0.8.seconds)
13
13
  validate_input(10)
14
14
  end
15
15
 
@@ -21,7 +21,7 @@ describe ReReplay, "advanced functions" do
21
21
  :rampup => [0.5, 2]
22
22
  }
23
23
  r.profile = profile
24
- lambda { r.run }.should take_between(2.05.seconds).and(2.2.seconds)
24
+ lambda { r.run }.should take_between(1.05.seconds).and(1.2.seconds)
25
25
  validate_input(10)
26
26
  end
27
27
 
@@ -35,7 +35,7 @@ describe ReReplay, "advanced functions" do
35
35
  :when_input_consumed => :loop
36
36
  }
37
37
  r.profile = profile
38
- lambda { r.run }.should take_between(3.seconds).and(3.1.seconds)
38
+ lambda { r.run }.should take_between(2.seconds).and(2.2.seconds)
39
39
  validate_input(10, 2)
40
40
  end
41
41
  end
@@ -1,4 +1,4 @@
1
- require 'spec/spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe ReReplay, "basic functions" do
4
4
  it "should process input from array" do
@@ -10,7 +10,7 @@ describe ReReplay, "basic functions" do
10
10
  input = urls.map {|i| [interval += 0.1, :get, i]}
11
11
 
12
12
  r = ReReplay::Runner.new(input)
13
- lambda { r.run }.should take_between(1.second).and(2.seconds)
13
+ lambda { r.run }.should take_between(0.seconds).and(1.second)
14
14
 
15
15
  urls.each {|url| WebMock.should have_requested(:get, url)}
16
16
  end
@@ -25,7 +25,7 @@ EOF
25
25
  stub_request(:get, "http://www.amazon.com/")
26
26
 
27
27
  r = ReReplay::Runner.new(input)
28
- lambda { r.run }.should take_between(1.second).and(2.seconds)
28
+ lambda { r.run }.should take_between(0.seconds).and(1.second)
29
29
 
30
30
  WebMock.should have_requested(:get, "http://www.google.com/")
31
31
  WebMock.should have_requested(:get, "http://www.amazon.com/")
@@ -1,4 +1,4 @@
1
- require 'spec/spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe ReReplay, "periodic monitors" do
4
4
  it "should work" do
@@ -9,9 +9,6 @@ describe ReReplay, "periodic monitors" do
9
9
  r = ReReplay::Runner.new(input)
10
10
  r.periodic_monitors << mem_monitor
11
11
 
12
- # the periodic monitor will start around `time_for_setup` (1 second)
13
- # and run once every ~0.23s thereafter.
14
- # Because the final request will finish around 1.75, this run four times
15
12
  r.run
16
13
  validate_input(3)
17
14
  mem_monitor.results.length.should == 4
@@ -26,9 +23,7 @@ describe ReReplay, "request monitors" do
26
23
  input = generate_input(3, :interval => 0.25)
27
24
  r = ReReplay::Runner.new(input)
28
25
  r.request_monitors << req_mon << delay_mon
29
- # the periodic monitor will start around `time_for_setup` (1 second)
30
- # and run once every ~0.23s thereafter.
31
- # Because the final request will finish around 1.75, this run four times
26
+
32
27
  r.run
33
28
  validate_input(3)
34
29
  req_mon.results.length.should == 3
@@ -48,7 +43,8 @@ started request 1:\\(http://microsoft.com/\\) at [\\d\\.]+
48
43
  started request 2:\\(http://amazon.com/\\) at [\\d\\.]+
49
44
  - finished request 2, status 200
50
45
  EOF
51
- capture_stdout { r.run }.should match(expected)
46
+ output = capture_stdout { r.run }
47
+ output.should match(expected)
52
48
  validate_input(3)
53
49
  end
54
50
  end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe ReReplay, "profile options" do
4
+ it "respects 'run_for' parameter" do
5
+ input = generate_input(3, :interval => 0.3)
6
+ r = ReReplay::Runner.new(input)
7
+ profile = {
8
+ :run_for => 0.5
9
+ }
10
+ r.profile = profile
11
+
12
+ lambda { r.run }.should take_between(0.5.seconds).and(0.6.seconds)
13
+ validate_input(1)
14
+ end
15
+
16
+ it "respects 'when_input_consumed' parameter when :stop" do
17
+ input = generate_input(3, :interval => 0.1)
18
+ r = ReReplay::Runner.new(input)
19
+ profile = {
20
+ :run_for => 10,
21
+ :when_input_consumed => :stop
22
+ }
23
+ r.profile = profile
24
+
25
+ lambda { r.run }.should take_between(0.3.seconds).and(0.5.seconds)
26
+ validate_input(3)
27
+ end
28
+
29
+ it "respects 'when_input_consumed' parameter when :loop" do
30
+ input = generate_input(1, :interval => 0.25)
31
+ r = ReReplay::Runner.new(input)
32
+ profile = {
33
+ :run_for => 0.6,
34
+ :when_input_consumed => :loop
35
+ }
36
+ r.profile = profile
37
+ req_mon = ReReplay::RequestTimeMonitor.new
38
+ r.request_monitors << req_mon
39
+
40
+ # normally this would take 1.5 seconds with :stop, but we're forcing it to loop and take 2 seconds
41
+ lambda { r.run }.should take_between(0.6.seconds).and(0.7.seconds)
42
+ req_mon.results.length.should == 2
43
+ validate_input(1, 2)
44
+ end
45
+
46
+ # eh, this isn't that good a test because WebMock causes #to_timeout requests to timeout immediately
47
+ # but it tests that timeout works
48
+ it "works with timeouts" do
49
+ input = generate_input(1, :interval => 0.25, :timeout => true)
50
+ r = ReReplay::Runner.new(input)
51
+ profile = {
52
+ :timeout => 10
53
+ }
54
+ r.profile = profile
55
+ r.request_monitors << ReReplay::TimeoutFailer.new
56
+
57
+ lambda { lambda { r.run }.should raise_error(StandardError, /TimeoutFailer/) }.should take_between(0.25.seconds).and(0.5.seconds)
58
+ validate_input(1)
59
+ end
60
+
61
+ end
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rereplay
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
9
- version: "0.1"
8
+ - 2
9
+ version: "0.2"
10
10
  platform: ruby
11
11
  authors:
12
12
  - Max Aller
@@ -14,59 +14,13 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-10-25 00:00:00 -07:00
17
+ date: 2010-10-30 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies:
20
- - !ruby/object:Gem::Dependency
21
- name: eventmachine
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
- none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- hash: 59
29
- segments:
30
- - 0
31
- - 12
32
- - 10
33
- version: 0.12.10
34
- - - <
35
- - !ruby/object:Gem::Version
36
- hash: 17
37
- segments:
38
- - 0
39
- - 13
40
- version: "0.13"
41
- type: :runtime
42
- version_requirements: *id001
43
- - !ruby/object:Gem::Dependency
44
- name: em-http-request
45
- prerelease: false
46
- requirement: &id002 !ruby/object:Gem::Requirement
47
- none: false
48
- requirements:
49
- - - ">="
50
- - !ruby/object:Gem::Version
51
- hash: 11
52
- segments:
53
- - 0
54
- - 2
55
- - 14
56
- version: 0.2.14
57
- - - <
58
- - !ruby/object:Gem::Version
59
- hash: 13
60
- segments:
61
- - 0
62
- - 3
63
- version: "0.3"
64
- type: :runtime
65
- version_requirements: *id002
66
20
  - !ruby/object:Gem::Dependency
67
21
  name: rspec
68
22
  prerelease: false
69
- requirement: &id003 !ruby/object:Gem::Requirement
23
+ requirement: &id001 !ruby/object:Gem::Requirement
70
24
  none: false
71
25
  requirements:
72
26
  - - ">="
@@ -83,11 +37,11 @@ dependencies:
83
37
  - 3
84
38
  version: "3"
85
39
  type: :development
86
- version_requirements: *id003
40
+ version_requirements: *id001
87
41
  - !ruby/object:Gem::Dependency
88
42
  name: webmock
89
43
  prerelease: false
90
- requirement: &id004 !ruby/object:Gem::Requirement
44
+ requirement: &id002 !ruby/object:Gem::Requirement
91
45
  none: false
92
46
  requirements:
93
47
  - - ">="
@@ -106,11 +60,11 @@ dependencies:
106
60
  - 5
107
61
  version: "1.5"
108
62
  type: :development
109
- version_requirements: *id004
63
+ version_requirements: *id002
110
64
  - !ruby/object:Gem::Dependency
111
65
  name: active_support
112
66
  prerelease: false
113
- requirement: &id005 !ruby/object:Gem::Requirement
67
+ requirement: &id003 !ruby/object:Gem::Requirement
114
68
  none: false
115
69
  requirements:
116
70
  - - ">="
@@ -129,7 +83,7 @@ dependencies:
129
83
  - 1
130
84
  version: "3.1"
131
85
  type: :development
132
- version_requirements: *id005
86
+ version_requirements: *id003
133
87
  description: Replay or script traffic in order to track performance of your site over time.
134
88
  email:
135
89
  - nanodeath@gmail.com
@@ -141,6 +95,7 @@ extra_rdoc_files: []
141
95
 
142
96
  files:
143
97
  - .gitignore
98
+ - .infinity_test
144
99
  - Gemfile
145
100
  - Gemfile.lock
146
101
  - LICENSE
@@ -153,13 +108,13 @@ files:
153
108
  - lib/rereplay/monitors/request_time_monitor.rb
154
109
  - lib/rereplay/monitors/timeout_failer.rb
155
110
  - lib/rereplay/monitors/verbose_monitor.rb
156
- - lib/rereplay/runner.rb
111
+ - lib/rereplay/ruby_runner.rb
157
112
  - lib/rereplay/version.rb
158
113
  - rereplay.gemspec
159
- - spec/advanced.rb
160
- - spec/basic.rb
161
- - spec/monitor.rb
162
- - spec/profile.rb
114
+ - spec/advanced_spec.rb
115
+ - spec/basic_spec.rb
116
+ - spec/monitor_spec.rb
117
+ - spec/profile_spec.rb
163
118
  - spec/spec_custom_matchers.rb
164
119
  - spec/spec_helper.rb
165
120
  has_rdoc: true
@@ -1,200 +0,0 @@
1
- module ReReplay
2
- class Runner
3
- attr_accessor :periodic_monitors
4
- attr_accessor :request_monitors
5
-
6
- def initialize(input=nil)
7
- if(!input.nil?)
8
- self.input = input
9
- end
10
- @periodic_monitors = []
11
- @request_monitors = []
12
- end
13
-
14
- def input=(input)
15
- if(input.is_a? Array)
16
- @input = input
17
- elsif(input.respond_to? :readlines)
18
- @input = input.readlines
19
- elsif(input.respond_to? :split)
20
- @input = input.split("\n").map do |i|
21
- i = i.strip.split(",").map {|j| j.strip}
22
- i[0] = i[0].to_f
23
- i[1] = i[1].to_sym
24
- i
25
- end
26
- else
27
- raise "Invalid input, expected Array, #readlines, or #split"
28
- end
29
- end
30
-
31
- def validate_input
32
- if(@input.nil? || @input.empty?)
33
- raise ArgumentError, "Nothing to process (input was empty)"
34
- end
35
- valid_methods = [:get, :head]
36
- @input.each_with_index do |a, i|
37
- if(!a[0].is_a? Numeric)
38
- raise ArgumentError, "Expected element at index 0 of input #{i+1} to be Numeric; was #{a[0]}"
39
- end
40
- if(!a[1].is_a?(Symbol) || !valid_methods.include?(a[1]))
41
- raise ArgumentError, "Expected element at index 1 of input #{i+1} to be a symbol in #{valid_methods.inspect}; was #{a[1].inspect}"
42
- end
43
- if(!a[2].is_a? String)
44
- raise ArgumentError, "Expected element at index 2 of input #{i+1} to be a String; was #{a[2]}"
45
- end
46
- if(!a[3].nil? && !a[3].is_a?(Hash))
47
- raise ArgumentError, "Expected element at index 3 of input #{i+1} to be nil or a Hash; was #{a[3]}"
48
- end
49
- # TODO post data
50
- end
51
- end
52
-
53
- def run
54
- validate_input
55
- p = profile
56
- done_count = 0
57
- # request monitors with a start method
58
- request_monitors_start = request_monitors.select {|mon| mon.respond_to? :start}
59
- # request monitors with a finish method
60
- request_monitors_finish = request_monitors.select {|mon| mon.respond_to? :finish}
61
- EM::run do
62
- EM.set_quantum(p[:timer_granularity])
63
- start = Time.now
64
- setup_time = p[:time_for_setup]
65
- actual_start = start + setup_time
66
-
67
- loop_count = 1
68
-
69
- max_time = @input.max {|a,b| a[0] <=> b[0]}[0]
70
- #avg_time = @input.inject(0){|memo, i| i[0] + memo}.to_f / @input.length
71
- if(p[:when_input_consumed] == :loop)
72
- if(max_time < p[:run_for])
73
- loop_count = (p[:run_for].to_f / max_time).ceil
74
- end
75
- end
76
-
77
- if(loop_count > 1)
78
- # If we need to have multiple iterations of the data,
79
- # we pad that on here
80
- new_inputs = []
81
- 2.upto(loop_count) do |loop|
82
- new_input = @input.map do |i|
83
- new_i = i.dup
84
- new_i[0] += max_time * (loop - 1)
85
- new_i
86
- end
87
- new_inputs << new_input
88
- end
89
- new_inputs.each {|input| @input += input}
90
- end
91
- real_max_time = [max_time * loop_count, p[:run_for]].min
92
- if(p[:rampup][0] != p[:rampup][1] || p[:rampup][0] != 1.0)
93
- case p[:rampup_method]
94
- when :linear
95
- sr = 1.0 / p[:rampup][0]
96
- fr = 1.0 / p[:rampup][1]
97
- prev_time = 0
98
- new_prev_time = 0
99
- @input.map! do |a|
100
- time = a[0].to_f
101
- percent = time / real_max_time
102
- fraction = sr + (fr - sr)*(time / real_max_time)
103
- tmp = a[0]
104
- a[0] = (time - prev_time)*fraction + new_prev_time
105
- prev_time = tmp
106
- new_prev_time = a[0]
107
- a
108
- end
109
- end
110
- end
111
- total_urls = @input.length
112
-
113
- requests = []
114
- # pregenerate requests
115
- @input.each do |a|
116
- requests << EventMachine::HttpRequest.new(a[2])
117
- end
118
-
119
- @input.each_with_index do |a, i|
120
- scheduled_start = a[0]
121
- request = OpenStruct.new(:url => a[2], :scheduled_start => scheduled_start, :index => i, :http_method => a[1])
122
- delay = actual_start + scheduled_start
123
- if(delay < Time.now)
124
- raise "Not enough time allotted for setup! Try increasing time_for_setup in your profile."
125
- end
126
- delay -= Time.now
127
- EM::add_timer(delay) do
128
- EM.defer do
129
- begin
130
- request.actual_start = Time.now - actual_start
131
- http = requests[i].send(request.http_method, :timeout => p[:timeout])
132
- request_monitors_start.each {|mon| mon.start(request)}
133
- rescue => e
134
- EM.next_tick do
135
- raise e
136
- end
137
- end
138
- callback = lambda {
139
- time_finished = Time.now - actual_start
140
- request.finish = time_finished
141
- request.status = http.response_header.status
142
- if(request.status == 0)
143
- request.status = :timeout
144
- end
145
- begin
146
- request_monitors_finish.each {|mon| mon.finish(request)}
147
- rescue => e
148
- EM.next_tick do
149
- raise e
150
- end
151
- end
152
- done_count += 1
153
- if(done_count == total_urls && p[:when_input_consumed] == :stop)
154
- EM.stop
155
- end
156
- }
157
- http.errback { callback.call }
158
- http.callback { callback.call }
159
- end
160
- end
161
- end
162
- periodic_monitors.each do |mon|
163
- interval = mon.respond_to?(:interval) ? mon.interval : 5
164
- EM::add_timer(actual_start - Time.now - interval) do
165
- EM::add_periodic_timer(interval) do
166
- mon.tick(Time.now - actual_start)
167
- end
168
- end
169
- end
170
- run_for = actual_start + p[:run_for] - Time.now
171
- EM::add_timer(run_for) do
172
- #puts "run_for hit (#{run_for})"
173
- EM.stop
174
- end
175
- end
176
- end
177
-
178
- def profile=(new_profile)
179
- @profile = {
180
- :time_for_setup => 1,
181
- :timer_granularity => 50,
182
- :run_for => 5,
183
- :when_input_consumed => :stop,
184
- :timeout => 1,
185
- :rampup => [1.0, 1.0],
186
- :rampup_method => :linear
187
- }
188
- if(new_profile.is_a? Hash)
189
- @profile.merge!(new_profile)
190
- end
191
- # TODO validate profile
192
- end
193
- def profile
194
- if(@profile.nil?)
195
- self.profile = {}
196
- end
197
- @profile
198
- end
199
- end
200
- end
@@ -1,90 +0,0 @@
1
- require 'spec/spec_helper'
2
-
3
- describe ReReplay, "profile options" do
4
- it "respects 'time_for_setup' parameter" do
5
- input = generate_input(2)
6
-
7
- r = ReReplay::Runner.new(input)
8
- profile = {
9
- :time_for_setup => 0.25
10
- }
11
- r.profile = profile
12
- lambda { r.run }.should take_between(0.45.seconds).and(0.6.seconds)
13
- validate_input(2)
14
- end
15
- it "respects 'timer_granularity' parameter" do
16
- input = generate_input(2)
17
-
18
- r = ReReplay::Runner.new(input)
19
- profile = {
20
- :timer_granularity => 1000
21
- }
22
- r.profile = profile
23
-
24
- # normally this would finish at around 1.2 seconds, but with such a high
25
- # timer resolution, it rounds up from 0.1 to 1
26
- lambda { r.run }.should take_between(2.seconds).and(3.seconds)
27
- validate_input(2)
28
- end
29
- it "respects 'run_for' parameter" do
30
- input = generate_input(3, :interval => 1)
31
- r = ReReplay::Runner.new(input)
32
- profile = {
33
- :run_for => 1
34
- }
35
- r.profile = profile
36
-
37
- # normally this would run for the full 4 seconds, but with run_for fixed at 2,
38
- # it will stop then
39
- lambda { r.run }.should take_between(2.seconds).and(2.1.seconds)
40
- validate_input(1)
41
- end
42
-
43
- it "respects 'when_input_consumed' parameter when :stop" do
44
- input = generate_input(3, :interval => 0.5)
45
- r = ReReplay::Runner.new(input)
46
- profile = {
47
- :run_for => 10,
48
- :when_input_consumed => :stop
49
- }
50
- r.profile = profile
51
-
52
- lambda { r.run }.should take_between(2.5.seconds).and(2.7.seconds)
53
- validate_input(3)
54
- end
55
-
56
- it "respects 'when_input_consumed' parameter when :loop" do
57
- input = generate_input(1, :interval => 0.5)
58
- r = ReReplay::Runner.new(input)
59
- profile = {
60
- :run_for => 1.1,
61
- :when_input_consumed => :loop
62
- }
63
- r.profile = profile
64
- req_mon = ReReplay::RequestTimeMonitor.new
65
- r.request_monitors << req_mon
66
-
67
- # normally this would take 1.5 seconds with :stop, but we're forcing it to loop and take 2 seconds
68
- lambda { r.run }.should take_between(2.seconds).and(2.2.seconds)
69
- req_mon.results.length.should == 2
70
- validate_input(1, 2)
71
- end
72
-
73
- # eh, this isn't that good a test because WebMock causes #to_timeout requests to timeout immediately
74
- it "respects 'timeout' parameter" do
75
- input = generate_input(1, :interval => 0.25, :timeout => true)
76
- r = ReReplay::Runner.new(input)
77
- profile = {
78
- :timeout => 10,
79
- :time_for_setup => 0.25
80
- }
81
- r.profile = profile
82
- r.request_monitors << ReReplay::TimeoutFailer.new
83
-
84
- # normally this would take 4 seconds with :stop, but after 2 seconds we hit the timeout
85
-
86
- lambda { lambda { r.run }.should raise_error(StandardError, /TimeoutFailer/) }.should take_between(0.5.seconds).and(0.7.seconds)
87
- validate_input(1)
88
- end
89
-
90
- end