em-http-test 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "eventmachine"
4
+ gem "em-http-request"
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ em-http-test
2
+ ============
3
+
4
+ Ruby EventMachine-base HTTP load testing library that allows writing simple web application testing scenarios
5
+ that are run using high performance asynchronous IO powered by EventMachine and em-http-request.
6
+
7
+ Requirements are EventMachine, em-http-request and fiber.
8
+
9
+ Usage is very simple, for example:
10
+
11
+ #!/usr/bin/ruby
12
+
13
+ require 'em-http-test'
14
+
15
+ concurrency = 10000
16
+ runtime = 300
17
+
18
+ p EM::HttpTest::run(concurrency, runtime) do
19
+ response = EM::HttpTest::post('http://mytestapp/login') :query => { 'username' => 'oded', 'password' => '123' }
20
+ sessionid = response['PHPSESSIONID']
21
+ raise EM::HttpTest::TestFailure, "Error in login" unless response.response_header.status == 200
22
+ response = EM::HttpTest::get('http://mytestapp/list') :query => { 'filter' => 'all' },
23
+ :head { 'PHPSESSIONID' => sessionid }
24
+ raise EM::HttpTest::TestFailure, "Error in list" unless response.response_header.status == 200
25
+ end
26
+
27
+ The block passed to `run()` will be executed continously for 300 seconds, with 10,000 sessions running simoultaneously.
28
+ The `EventMachine::HttpTest::post` and `EventMachine::HttpTest::get` are used to dispatch the HTTP requests in an
29
+ apparently synchronous manner allowing test sessions to be written using a simple programming model.
30
+
31
+ The named arguments passed to `post()` and `get()` are passed to em-http-request and any arguments supported by
32
+ em-http-request can be used. To facilitate ease of use, `HttpTest` will convert `:query` data to `:body` content
33
+ when using `post()`. The return value for `post()` and `get()` is the em-http-request client with the response
34
+ data, which can be examined according to the em-http-request API.
35
+
36
+ To abort a testing session without aborting the entire load test, raise `EventMachine::HttpTest::TestFailure`. All such
37
+ errors will be collected, the aborted session will be counted as a test failure and the exception data will be available
38
+ in the test results summary.
39
+
40
+ The return value from `run()` is a hash with the following fields:
41
+
42
+ * `:total` - total number of sessions that were run
43
+ * `:success` - total number of sessions that completed without raising a TestFailure
44
+ * `:failure` - total number of sessions that raised a TestFailure
45
+ * `:failures` - array containing the exception data element for each failed session
46
+ * `:failure_rate` - the rate of session failures compared to completed sessions (0 to 1)
47
+ * `:min` - the number of seconds that the fastest session completed in
48
+ * `:max` - the number of seconds that the slowest session completed in
49
+ * `:average` - average number of seconds for all test sessions (including failures)
50
+ * `:percentile95` - 95% of the sessions completed in this number of seconds or less
51
+
52
+ Results in seconds could be fractional seconds.
@@ -0,0 +1,26 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ require 'em-http-test/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "em-http-test"
7
+ s.version = EventMachine::HttpTest::VERSION
8
+
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Oded Arbel"]
11
+ s.email = ["oded@heptagon.co.il"]
12
+ s.homepage = "https://github.com/guss77/em-http-test"
13
+ s.summary = "EventMachine based, high performance web application load test framework"
14
+ s.description = s.summary
15
+ #s.rubyforge_project = "em-http-test"
16
+
17
+ #s.add_dependency "eventmachine", ">= 0.12.0"
18
+ s.add_dependency "em-http-request", ">= 0.3.0"
19
+
20
+ s.add_development_dependency "rspec"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ #s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+ end
@@ -0,0 +1,13 @@
1
+ # EventMachine based HTTP load testing tool
2
+ #
3
+ # Author:: Oded Arbel (mailto:oded@heptagon.co.il)
4
+ # Copyright:: Copyright (c) 2013 Heptagon Software
5
+ # License:: GPL v3
6
+
7
+ # define name space modules
8
+
9
+ module EventMachine
10
+ end
11
+
12
+ module EventMachine::HttpTest
13
+ end
@@ -0,0 +1,74 @@
1
+ # EventMachine based HTTP load testing tool
2
+ #
3
+ # Author:: Oded Arbel (mailto:oded@heptagon.co.il)
4
+ # Copyright:: Copyright (c) 2013 Heptagon Software
5
+ # License:: GPL v3
6
+
7
+ module EventMachine::HttpTest
8
+
9
+ # Run the specified test in the specified concurrency
10
+ class LoadTest
11
+ include EM::Deferrable
12
+
13
+ def initialize(concurrency, &block)
14
+ @concurrency = concurrency
15
+ @runners = [] # list of currently running tests
16
+ @failureCB = [] # list of failure callbacks
17
+ @successCB = [] # list of success callbacks
18
+ @test = block
19
+ @running = true
20
+
21
+ @concurrency.times { self.startTest }
22
+ end
23
+
24
+ # stop the test and don't execute any more test runners
25
+ def abort
26
+ @running = false
27
+ end
28
+
29
+ # register a failure handler
30
+ def onfailure(&block)
31
+ @failureCB << block
32
+ end
33
+
34
+ # register a success handler
35
+ def onsuccess(&block)
36
+ @successCB << block
37
+ end
38
+
39
+ def count
40
+ return @runners.size
41
+ end
42
+
43
+ # Add a new test runner
44
+ def startTest
45
+ return unless @running # don't start new tests if the EventMachine is stopping
46
+ startAt = Time.new
47
+ # create a new runner and add it to the queue
48
+ runner = TestRunner.new(Fiber.new { @test.call })
49
+ @runners << runner
50
+ runner.errback do |e| # if the runner failed
51
+ @runners.delete(runner) # remove it from the queue
52
+ @failureCB.each { |b| b.call(Time.new - startAt, e) } # notify the failure handler
53
+ self.tick # then tick the load test
54
+ end
55
+ runner.callback do # if the runnner completed
56
+ @runners.delete(runner) # remove it from the queue
57
+ @successCB.each { |b| b.call(Time.new - startAt) } # notify the success handler
58
+ self.tick # then tick the load test
59
+ end
60
+ end
61
+
62
+ # Replenish test runners
63
+ def tick
64
+ if !@running and @runners.count < 1
65
+ EM.stop
66
+ return
67
+ end
68
+ missing = @concurrency - @runners.count
69
+ missing.times { self.startTest }
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,20 @@
1
+ # EventMachine based HTTP load testing tool
2
+ #
3
+ # Author:: Oded Arbel (mailto:oded@heptagon.co.il)
4
+ # Copyright:: Copyright (c) 2013 Heptagon Software
5
+ # License:: GPL v3
6
+
7
+ module EventMachine::HttpTest
8
+
9
+ # Exception to raise in test code to signify a test failure
10
+ class TestFailure < Exception
11
+
12
+ attr_accessor :message
13
+
14
+ def initialize(text)
15
+ self.message = text;
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,42 @@
1
+ # EventMachine based HTTP load testing tool
2
+ #
3
+ # Author:: Oded Arbel (mailto:oded@heptagon.co.il)
4
+ # Copyright:: Copyright (c) 2013 Heptagon Software
5
+ # License:: GPL v3
6
+
7
+ module EventMachine::HttpTest
8
+
9
+ # Base test request object that contains the test data
10
+ class Request
11
+
12
+ def initialize(type, url, options = {})
13
+ @type = type
14
+ @url = url
15
+ @options = options
16
+ if @type == :POST and !@options[:query].nil?
17
+ @options[:body] = @options[:query]
18
+ @options.delete(:query)
19
+ end
20
+ end
21
+
22
+ def toRequest
23
+ case @type
24
+ when :GET
25
+ EM::HttpRequest.new(@url).get @options
26
+ when :POST
27
+ EM::HttpRequest.new(@url).post @options
28
+ end
29
+ end
30
+ end
31
+
32
+ # do a evented HTTP GET
33
+ def self.get(url, options = {})
34
+ Fiber.yield Request.new(:GET, url, options)
35
+ end
36
+
37
+ # do a evented HTTP POST
38
+ def self.post(url, options = {})
39
+ Fiber.yield Request.new(:POST, url, options)
40
+ end
41
+
42
+ end
@@ -0,0 +1,53 @@
1
+ # EventMachine based HTTP load testing tool
2
+ #
3
+ # Author:: Oded Arbel (mailto:oded@heptagon.co.il)
4
+ # Copyright:: Copyright (c) 2013 Heptagon Software
5
+ # License:: GPL v3
6
+
7
+ # Run a single HTTP test by iterating through the test operatins until the test
8
+ # either raises TestFailure or completes successfully
9
+ module EventMachine::HttpTest
10
+
11
+ class TestRunner
12
+ include EM::Deferrable
13
+
14
+ def initialize(f)
15
+ @fiber = f
16
+ @testreq = @fiber.resume # first call, so no data to test
17
+ process
18
+ end
19
+
20
+ # Run a step of the test by calling the test generated URL
21
+ # and submitting the results to be processed
22
+ def process
23
+ unless @fiber.alive?
24
+ # if the fiber has completed, then we succeeded
25
+ self.succeed()
26
+ return
27
+ end
28
+
29
+ # do another run
30
+ request = @testreq.toRequest
31
+ request.errback { |r| self.fail("HTTP error calling #{r.last_effective_url}: #{r.response_header.status}") }
32
+ request.callback do |r|
33
+ if r.response_header.status >= 400
34
+ self.fail("HTTP error #{r.response_header.status}")
35
+ next
36
+ end
37
+
38
+ begin
39
+ @testreq = @fiber.resume r
40
+ case when @testreq.nil?
41
+ self.succeed("Test is done")
42
+ else
43
+ self.process
44
+ end
45
+ rescue TestFailure => e
46
+ self.fail(e.message)
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,5 @@
1
+ module EventMachine
2
+ class HttpTest
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,57 @@
1
+ # EventMachine based HTTP load testing tool
2
+ #
3
+ # Author:: Oded Arbel (mailto:oded@heptagon.co.il)
4
+ # Copyright:: Copyright (c) 2013 Heptagon Software
5
+ # License:: GPL v3
6
+
7
+ require 'rubygems'
8
+ require 'eventmachine'
9
+ require 'em-http'
10
+ require 'fiber'
11
+
12
+ require 'em-http-test/definitions'
13
+ require 'em-http-test/test-failure'
14
+ require 'em-http-test/test-runner'
15
+ require 'em-http-test/load-test'
16
+ require 'em-http-test/test-request'
17
+
18
+ module EventMachine::HttpTest
19
+
20
+ # run a load test by executing the test block multiple times in the
21
+ # specified concurrency. The test will be executed for a total of runtime seconds
22
+ def self.run(concurrency, runtime, &block)
23
+ stats = {
24
+ :total => 0,
25
+ :success => 0,
26
+ :failure => 0,
27
+ :failures => [],
28
+ }
29
+
30
+ successTimes = []
31
+
32
+ if block.nil?
33
+ stats[:error] = 'Invalid test block'
34
+ return stats
35
+ end
36
+
37
+ startTime = Time.new
38
+
39
+ EventMachine.run do
40
+ test = LoadTest.new(concurrency) { block.call }
41
+ test.onsuccess { |t| stats[:success] += 1; stats[:total] += 1; successTimes << t }
42
+ test.onfailure { |t,e| stats[:failure] += 1; stats[:total] += 1; successTimes << t; stats[:failures] << e }
43
+ EventMachine.add_periodic_timer(1) { puts "#{stats[:total]} tests run in #{(Time.new - startTime).to_i} seconds" }
44
+ EventMachine.add_timer(runtime) { test.abort }
45
+ end
46
+
47
+ successTimes = successTimes.sort
48
+ stats[:average] = (successTimes.reduce(:+).to_f / successTimes.size).round(2)
49
+ stats[:percentile95] = (successTimes[(successTimes.count*0.95).to_i]).round(2)
50
+ stats[:min] = (successTimes[0]).round(2)
51
+ stats[:max] = (successTimes[-1]).round(2)
52
+ stats[:failure_rate] = (stats[:failure].to_f / stats[:total]).round(2)
53
+
54
+ return stats
55
+ end
56
+
57
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-http-test
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Oded Arbel
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: em-http-request
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.3.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.3.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: EventMachine based, high performance web application load test framework
47
+ email:
48
+ - oded@heptagon.co.il
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - Gemfile
54
+ - README.md
55
+ - em-http-test.gemspec
56
+ - lib/em-http-test.rb
57
+ - lib/em-http-test/definitions.rb
58
+ - lib/em-http-test/load-test.rb
59
+ - lib/em-http-test/test-failure.rb
60
+ - lib/em-http-test/test-request.rb
61
+ - lib/em-http-test/test-runner.rb
62
+ - lib/em-http-test/version.rb
63
+ homepage: https://github.com/guss77/em-http-test
64
+ licenses: []
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 1.8.23
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: EventMachine based, high performance web application load test framework
87
+ test_files: []