rereplay 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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