trebuchet-lt 0.0.1
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.
- data/.gitignore +4 -0
- data/.rvmrc +1 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +41 -0
- data/README.md +26 -0
- data/Rakefile +8 -0
- data/bin/trebuchet +22 -0
- data/lib/trebuchet.rb +8 -0
- data/lib/trebuchet/client.rb +5 -0
- data/lib/trebuchet/parser.rb +35 -0
- data/lib/trebuchet/result.rb +32 -0
- data/lib/trebuchet/server.rb +94 -0
- data/test/fixtures/siege_output.txt +16 -0
- data/test/parser_test.rb +20 -0
- data/test/result_test.rb +16 -0
- data/test/test_helper.rb +5 -0
- data/trebuchet +8 -0
- data/trebuchet.gemspec +24 -0
- data/trebuchet.yml.example +8 -0
- metadata +98 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rvm use 1.9.3@trebuchet --create
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
GEM
|
|
2
|
+
remote: https://www.rubygems.org/
|
|
3
|
+
specs:
|
|
4
|
+
builder (3.2.0)
|
|
5
|
+
celluloid (0.13.0)
|
|
6
|
+
timers (>= 1.0.0)
|
|
7
|
+
coderay (1.0.9)
|
|
8
|
+
excon (0.20.1)
|
|
9
|
+
fog (1.10.1)
|
|
10
|
+
builder
|
|
11
|
+
excon (~> 0.20)
|
|
12
|
+
formatador (~> 0.2.0)
|
|
13
|
+
mime-types
|
|
14
|
+
multi_json (~> 1.0)
|
|
15
|
+
net-scp (~> 1.1)
|
|
16
|
+
net-ssh (>= 2.1.3)
|
|
17
|
+
nokogiri (~> 1.5.0)
|
|
18
|
+
ruby-hmac
|
|
19
|
+
formatador (0.2.4)
|
|
20
|
+
method_source (0.8.1)
|
|
21
|
+
mime-types (1.23)
|
|
22
|
+
multi_json (1.7.2)
|
|
23
|
+
net-scp (1.1.0)
|
|
24
|
+
net-ssh (>= 2.6.5)
|
|
25
|
+
net-ssh (2.6.7)
|
|
26
|
+
nokogiri (1.5.9)
|
|
27
|
+
pry (0.9.12.1)
|
|
28
|
+
coderay (~> 1.0.5)
|
|
29
|
+
method_source (~> 0.8)
|
|
30
|
+
slop (~> 3.4)
|
|
31
|
+
ruby-hmac (0.4.0)
|
|
32
|
+
slop (3.4.4)
|
|
33
|
+
timers (1.1.0)
|
|
34
|
+
|
|
35
|
+
PLATFORMS
|
|
36
|
+
ruby
|
|
37
|
+
|
|
38
|
+
DEPENDENCIES
|
|
39
|
+
celluloid
|
|
40
|
+
fog
|
|
41
|
+
pry
|
data/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Trebuchet
|
|
2
|
+
=========
|
|
3
|
+
|
|
4
|
+
Trebuchet is a gem for distributed performance testing. It spins up
|
|
5
|
+
arbitrary ec2 micro servers, and does performance testing from them.
|
|
6
|
+
|
|
7
|
+
To set up trebuchet, install it normally with gem, then copy down the
|
|
8
|
+
trebuchet.yml.example and rename it trebuchet.yml. Then update it with
|
|
9
|
+
your revelvant information.
|
|
10
|
+
|
|
11
|
+
To run arbitrary load tests, you can use trebuchet from the command line
|
|
12
|
+
|
|
13
|
+
trebuchet -s 5 -c 100 -t 20M http://www.example.com
|
|
14
|
+
|
|
15
|
+
-s: Number of Servers to spin up
|
|
16
|
+
-c: Number of Concurrent Users per Server
|
|
17
|
+
-t: Amount of Time to run the test (ex. 10S, 5M, 1H)
|
|
18
|
+
|
|
19
|
+
When you run from the command line, Trebuchet will spin up the required
|
|
20
|
+
servers in your
|
|
21
|
+
|
|
22
|
+
Alternatively, you can invoke Trebuchet's internal ruby classes to spin
|
|
23
|
+
up servers, and run multiple load tests then terminate all the servers
|
|
24
|
+
it spun up. It will not terminate any other servers you have running in
|
|
25
|
+
EC2.
|
|
26
|
+
|
data/Rakefile
ADDED
data/bin/trebuchet
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#! /usr/bin/env ruby
|
|
2
|
+
# Trebuchet Command Line
|
|
3
|
+
|
|
4
|
+
require "trebuchet"
|
|
5
|
+
|
|
6
|
+
trebuchet = Trebuchet::Server.new
|
|
7
|
+
servers = 1
|
|
8
|
+
if ARGV.include? "-s"
|
|
9
|
+
servers = Integer(ARGV[ARGV.index("-s")+ 1])
|
|
10
|
+
end
|
|
11
|
+
if ARGV.include? "-c"
|
|
12
|
+
trebuchet.concurrency = Integer(ARGV[ARGV.index("-c")+ 1])
|
|
13
|
+
end
|
|
14
|
+
if ARGV.include? "-t"
|
|
15
|
+
trebuchet.time = ARGV[ARGV.index("-t")+ 1]
|
|
16
|
+
end
|
|
17
|
+
trebuchet.url = ARGV.last
|
|
18
|
+
|
|
19
|
+
trebuchet.start servers
|
|
20
|
+
trebuchet.run
|
|
21
|
+
trebuchet.stop
|
|
22
|
+
|
data/lib/trebuchet.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Trebuchet
|
|
2
|
+
class Parser
|
|
3
|
+
attr_reader :failed_requests, :requests, :rps
|
|
4
|
+
def initialize siege_result
|
|
5
|
+
parse siege_result
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
def parse result
|
|
10
|
+
data_lines = result.lines.to_a[4..-1]
|
|
11
|
+
data_lines.each do |line|
|
|
12
|
+
method = "parse_#{line.split(":").first}".downcase.gsub(" ", "_").gsub("\b", "")
|
|
13
|
+
send(method, line.split(":").last) if private_methods.include? method.to_sym
|
|
14
|
+
end
|
|
15
|
+
if @requests.nil? or @failed_requests.nil? or @rps.nil?
|
|
16
|
+
puts result
|
|
17
|
+
@requests = 0 if @requests.nil?
|
|
18
|
+
@failed_requests = 0 if @failed_requests.nil?
|
|
19
|
+
@rps = 0 if @rps.nil?
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def parse_transactions data
|
|
24
|
+
@requests = Integer(data.match(/\d+/)[0])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def parse_failed_transactions data
|
|
28
|
+
@failed_requests = Integer(data.match(/\d+/)[0])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse_transaction_rate data
|
|
32
|
+
@rps = data.match(/[-+]?[0-9]*\.?[0-9]+/)[0].to_f
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Trebuchet
|
|
2
|
+
class Result
|
|
3
|
+
def initialize parse=nil
|
|
4
|
+
@requests = 0
|
|
5
|
+
@failed_requests = 0
|
|
6
|
+
@rps = 0.0
|
|
7
|
+
add_parse parse if parse
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add_parse parse
|
|
11
|
+
@requests = requests + parse.requests
|
|
12
|
+
@failed_requests = failed_requests + parse.failed_requests
|
|
13
|
+
@rps = rps + parse.rps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s
|
|
17
|
+
"Requests: #{@requests}\nFailed Requests: #{@failed_requests}\nRequests Per Second: #{@rps}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def requests
|
|
21
|
+
@requests || 0
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def failed_requests
|
|
25
|
+
@failed_requests || 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def rps
|
|
29
|
+
@rps || 0
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module Trebuchet
|
|
2
|
+
class Server
|
|
3
|
+
attr_accessor :concurrency, :config, :connections, :instances, :result, :time, :url
|
|
4
|
+
|
|
5
|
+
def initialize config=nil
|
|
6
|
+
if File.exists?("trebuchet.yml")
|
|
7
|
+
file = File.open('trebuchet.yml')
|
|
8
|
+
elsif File.exists?("~/.trebuchet")
|
|
9
|
+
file = File.open("~/.trebuchet")
|
|
10
|
+
end
|
|
11
|
+
@config = config || YAML::load(file)
|
|
12
|
+
@concurrency = "1"
|
|
13
|
+
@time = "1M"
|
|
14
|
+
@url = "http://www.example.com"
|
|
15
|
+
regions = @config.fetch("aws-config", {}).fetch("regions", ["us-east-1"])
|
|
16
|
+
|
|
17
|
+
@connections = []
|
|
18
|
+
regions.each do |region|
|
|
19
|
+
@connections << Fog::Compute.new(:provider => "AWS", :aws_access_key_id => @config.fetch("aws-config", {}).fetch("access_key_id"), :aws_secret_access_key => @config.fetch("aws-config", {}).fetch("access_key_secret"), :region => region)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
refresh_instances
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def start num_of_servers=1
|
|
26
|
+
puts "Starting #{num_of_servers} servers!"
|
|
27
|
+
futures = []
|
|
28
|
+
num_of_servers.times do
|
|
29
|
+
futures << Celluloid::Future.new do
|
|
30
|
+
@connections.sample.servers.bootstrap(:public_key_path => '~/.ssh/id_rsa.pub', :username => 'ubuntu', :tags => {:temporary => true}) rescue Net::SSH::Disconnect
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
futures.each { |future| future.value }
|
|
34
|
+
puts "#{num_of_servers} Servers Started!"
|
|
35
|
+
|
|
36
|
+
refresh_instances
|
|
37
|
+
|
|
38
|
+
puts "Setting up new instances!"
|
|
39
|
+
@instances.each { |instance| setup_instance instance }
|
|
40
|
+
|
|
41
|
+
puts "New instances configured and ready for use!"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def stop
|
|
45
|
+
puts "Shutting down all instances"
|
|
46
|
+
@instances.each { |instance| instance.destroy }
|
|
47
|
+
refresh_instances
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def run
|
|
51
|
+
puts "Beginning Load Test"
|
|
52
|
+
@result = Trebuchet::Result.new
|
|
53
|
+
futures = []
|
|
54
|
+
@instances.each do |instance|
|
|
55
|
+
futures << Celluloid::Future.new { run_siege instance }
|
|
56
|
+
end
|
|
57
|
+
futures.each { |future| future.value }
|
|
58
|
+
puts @result.to_s
|
|
59
|
+
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
def refresh_instances
|
|
65
|
+
@instances = []
|
|
66
|
+
@connections.each do |connection|
|
|
67
|
+
new_instance = connection.servers.select { |instance| instance.tags.fetch("temporary", false) and instance.state == "running" }
|
|
68
|
+
@instances << new_instance unless new_instance.empty?
|
|
69
|
+
end
|
|
70
|
+
@instances.flatten!
|
|
71
|
+
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def setup_instance instance
|
|
76
|
+
ssh = Net::SSH.start(instance.dns_name, "ubuntu")
|
|
77
|
+
ssh.exec! "sudo apt-get install siege"
|
|
78
|
+
ssh.exec! %{echo "verbose = false" > ~/.siegerc}
|
|
79
|
+
ssh.exec! %{sudo sh -c "echo '* hard nofile 1000000' >> /etc/security/limits.conf"}
|
|
80
|
+
ssh.exec! %{sudo sh -c "echo '* soft nofile 1000000' >> /etc/security/limits.conf"}
|
|
81
|
+
ssh.close
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def run_siege instance
|
|
85
|
+
ssh = Net::SSH.start(instance.dns_name, "ubuntu")
|
|
86
|
+
cmd = "siege -R ~/.siegerc -c #{concurrency} -t #{time} #{url}"
|
|
87
|
+
res = ssh.exec! cmd
|
|
88
|
+
ssh.close
|
|
89
|
+
parse = Trebuchet::Parser.new(res)
|
|
90
|
+
@result.add_parse(parse)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
** SIEGE 2.68
|
|
2
|
+
** Preparing 1 concurrent users for battle.
|
|
3
|
+
The server is now under siege...
|
|
4
|
+
Lifting the server siege.. done.
|
|
5
|
+
Transactions: 57 hits
|
|
6
|
+
Availability: 100.00 %
|
|
7
|
+
Elapsed time: 59.10 secs
|
|
8
|
+
Data transferred: 5.66 MB
|
|
9
|
+
Response time: 0.91 secs
|
|
10
|
+
Transaction rate: 0.96 trans/sec
|
|
11
|
+
Throughput: 0.10 MB/sec
|
|
12
|
+
Concurrency: 0.88
|
|
13
|
+
Successful transactions: 57
|
|
14
|
+
Failed transactions: 0
|
|
15
|
+
Longest transaction: 3.06
|
|
16
|
+
Shortest transaction: 0.74
|
data/test/parser_test.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class ParserTest < Test::Unit::TestCase
|
|
4
|
+
def setup
|
|
5
|
+
@parser = Trebuchet::Parser.new(File.open("test/fixtures/siege_output.txt"))
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_requests
|
|
9
|
+
assert_equal 57, @parser.requests
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_failed_requests
|
|
13
|
+
assert_equal 0, @parser.failed_requests
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_rps
|
|
17
|
+
assert_equal 0.96, @parser.rps
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
data/test/result_test.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
class ResultTest < Test::Unit::TestCase
|
|
4
|
+
def setup
|
|
5
|
+
@result = Trebuchet::Result.new
|
|
6
|
+
@parser = Trebuchet::Parser.new(File.open("test/fixtures/siege_output.txt"))
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_add_parse
|
|
10
|
+
@result.add_parse @parser
|
|
11
|
+
@result.add_parse @parser
|
|
12
|
+
assert_equal 114, @result.requests
|
|
13
|
+
assert_equal 0, @result.failed_requests
|
|
14
|
+
assert_equal 1.92, @result.rps
|
|
15
|
+
end
|
|
16
|
+
end
|
data/test/test_helper.rb
ADDED
data/trebuchet
ADDED
data/trebuchet.gemspec
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |s|
|
|
4
|
+
s.platform = Gem::Platform::RUBY
|
|
5
|
+
s.name = 'trebuchet-lt'
|
|
6
|
+
s.version = '0.0.1'
|
|
7
|
+
s.summary = 'Distributed Load Testing Made Easy'
|
|
8
|
+
s.description = 'Trebuchet is a gem for distributed performance testing. It spins up arbitrary ec2 micro servers, and does performance testing from them.'
|
|
9
|
+
|
|
10
|
+
s.required_ruby_version = '>= 1.9.3'
|
|
11
|
+
|
|
12
|
+
s.license = 'MIT'
|
|
13
|
+
|
|
14
|
+
s.author = 'Nathaniel Barnes'
|
|
15
|
+
s.email = 'Nathaniel.R.Barnes@gmail.com'
|
|
16
|
+
s.homepage = 'http://github.com/NateBarnes/trebuchet'
|
|
17
|
+
|
|
18
|
+
s.files = `git ls-files`.split("\n")
|
|
19
|
+
s.bindir = 'bin'
|
|
20
|
+
s.executables = ['trebuchet']
|
|
21
|
+
|
|
22
|
+
s.add_dependency 'celluloid'
|
|
23
|
+
s.add_dependency 'fog'
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: trebuchet-lt
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Nathaniel Barnes
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2013-05-03 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: celluloid
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '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'
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: fog
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ! '>='
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '0'
|
|
38
|
+
type: :runtime
|
|
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: Trebuchet is a gem for distributed performance testing. It spins up arbitrary
|
|
47
|
+
ec2 micro servers, and does performance testing from them.
|
|
48
|
+
email: Nathaniel.R.Barnes@gmail.com
|
|
49
|
+
executables:
|
|
50
|
+
- trebuchet
|
|
51
|
+
extensions: []
|
|
52
|
+
extra_rdoc_files: []
|
|
53
|
+
files:
|
|
54
|
+
- .gitignore
|
|
55
|
+
- .rvmrc
|
|
56
|
+
- Gemfile
|
|
57
|
+
- Gemfile.lock
|
|
58
|
+
- README.md
|
|
59
|
+
- Rakefile
|
|
60
|
+
- bin/trebuchet
|
|
61
|
+
- lib/trebuchet.rb
|
|
62
|
+
- lib/trebuchet/client.rb
|
|
63
|
+
- lib/trebuchet/parser.rb
|
|
64
|
+
- lib/trebuchet/result.rb
|
|
65
|
+
- lib/trebuchet/server.rb
|
|
66
|
+
- test/fixtures/siege_output.txt
|
|
67
|
+
- test/parser_test.rb
|
|
68
|
+
- test/result_test.rb
|
|
69
|
+
- test/test_helper.rb
|
|
70
|
+
- trebuchet
|
|
71
|
+
- trebuchet.gemspec
|
|
72
|
+
- trebuchet.yml.example
|
|
73
|
+
homepage: http://github.com/NateBarnes/trebuchet
|
|
74
|
+
licenses:
|
|
75
|
+
- MIT
|
|
76
|
+
post_install_message:
|
|
77
|
+
rdoc_options: []
|
|
78
|
+
require_paths:
|
|
79
|
+
- lib
|
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
81
|
+
none: false
|
|
82
|
+
requirements:
|
|
83
|
+
- - ! '>='
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: 1.9.3
|
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
|
+
none: false
|
|
88
|
+
requirements:
|
|
89
|
+
- - ! '>='
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
version: '0'
|
|
92
|
+
requirements: []
|
|
93
|
+
rubyforge_project:
|
|
94
|
+
rubygems_version: 1.8.25
|
|
95
|
+
signing_key:
|
|
96
|
+
specification_version: 3
|
|
97
|
+
summary: Distributed Load Testing Made Easy
|
|
98
|
+
test_files: []
|