blitz 0.1.0
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/.document +5 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +26 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +22 -0
- data/Rakefile +51 -0
- data/bin/blitz +11 -0
- data/blitz.gemspec +80 -0
- data/lib/blitz.rb +31 -0
- data/lib/blitz/client.rb +35 -0
- data/lib/blitz/command.rb +22 -0
- data/lib/blitz/command/api.rb +96 -0
- data/lib/blitz/command/curl.rb +272 -0
- data/lib/blitz/command/help.rb +22 -0
- data/lib/blitz/curl/error.rb +45 -0
- data/lib/blitz/curl/sprint.rb +108 -0
- data/lib/blitz/helper.rb +15 -0
- data/test/helper.rb +17 -0
- data/test/test_blitz.rb +6 -0
- metadata +182 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
gem "rest-client", "~> 1.6.1"
|
7
|
+
gem "json", "~> 1.4.6"
|
8
|
+
gem "json_pure", "~> 1.4.6"
|
9
|
+
gem "hexy", "~> 0.1.1"
|
10
|
+
|
11
|
+
# Add dependencies to develop your gem here.
|
12
|
+
# Include everything needed to run rake, tests, features, etc.
|
13
|
+
group :development do
|
14
|
+
gem "bundler", "~> 1.0.0"
|
15
|
+
gem "jeweler", "~> 1.5.1"
|
16
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
git (1.2.5)
|
5
|
+
hexy (0.1.1)
|
6
|
+
jeweler (1.5.1)
|
7
|
+
bundler (~> 1.0.0)
|
8
|
+
git (>= 1.2.5)
|
9
|
+
rake
|
10
|
+
json (1.4.6)
|
11
|
+
json_pure (1.4.6)
|
12
|
+
mime-types (1.16)
|
13
|
+
rake (0.8.7)
|
14
|
+
rest-client (1.6.1)
|
15
|
+
mime-types (>= 1.16)
|
16
|
+
|
17
|
+
PLATFORMS
|
18
|
+
ruby
|
19
|
+
|
20
|
+
DEPENDENCIES
|
21
|
+
bundler (~> 1.0.0)
|
22
|
+
hexy (~> 0.1.1)
|
23
|
+
jeweler (~> 1.5.1)
|
24
|
+
json (~> 1.4.6)
|
25
|
+
json_pure (~> 1.4.6)
|
26
|
+
rest-client (~> 1.6.1)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Mu Dynamics
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
## 
|
2
|
+
|
3
|
+
Make load and performance a fun sport.
|
4
|
+
|
5
|
+
* Run a sprint from around the world
|
6
|
+
* Rush your API and website to scale it out
|
7
|
+
* Condition your site around the clock
|
8
|
+
|
9
|
+
## Getting started
|
10
|
+
Login to [blitz.io](http://blitz.io) and in the blitz bar type:
|
11
|
+
--api-key
|
12
|
+
|
13
|
+
Now
|
14
|
+
gem install blitz
|
15
|
+
|
16
|
+
and run a sprint like this:
|
17
|
+
blitz curl --region california http://blitz.io
|
18
|
+
|
19
|
+
and you can rush like this:
|
20
|
+
blitz curl --region california --pattern 1-100:60 http://blitz.io
|
21
|
+
|
22
|
+
Copyright (c) 2011 Mu Dynamics. See LICENSE.txt for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'rake'
|
14
|
+
require 'jeweler'
|
15
|
+
require 'blitz'
|
16
|
+
Jeweler::Tasks.new do |gem|
|
17
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
18
|
+
gem.name = "blitz"
|
19
|
+
gem.homepage = "http://blitz.io"
|
20
|
+
gem.license = "MIT"
|
21
|
+
gem.summary = %Q{Make load and performance testing a fun sport}
|
22
|
+
gem.description = %Q{Make load and performance testing a fun sport}
|
23
|
+
gem.email = "kowsik@gmail.com"
|
24
|
+
gem.authors = ["pcapr"]
|
25
|
+
gem.version = Blitz::Version
|
26
|
+
gem.executables = ['blitz']
|
27
|
+
|
28
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
29
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
30
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
31
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
32
|
+
end
|
33
|
+
Jeweler::RubygemsDotOrgTasks.new
|
34
|
+
|
35
|
+
require 'rake/testtask'
|
36
|
+
Rake::TestTask.new(:test) do |test|
|
37
|
+
test.libs << 'lib' << 'test'
|
38
|
+
test.pattern = 'test/**/test_*.rb'
|
39
|
+
test.verbose = true
|
40
|
+
end
|
41
|
+
|
42
|
+
task :default => :test
|
43
|
+
|
44
|
+
require 'rake/rdoctask'
|
45
|
+
Rake::RDocTask.new do |rdoc|
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "blitz #{Blitz::Version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
51
|
+
|
data/bin/blitz
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'blitz'
|
7
|
+
|
8
|
+
argv = ARGV.dup
|
9
|
+
ARGV.clear
|
10
|
+
cmd = argv.shift.strip rescue 'help'
|
11
|
+
Blitz.run cmd, argv
|
data/blitz.gemspec
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{blitz}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["pcapr"]
|
12
|
+
s.date = %q{2011-03-10}
|
13
|
+
s.default_executable = %q{blitz}
|
14
|
+
s.description = %q{Make load and performance testing a fun sport}
|
15
|
+
s.email = %q{kowsik@gmail.com}
|
16
|
+
s.executables = ["blitz"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt",
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
"Gemfile",
|
24
|
+
"Gemfile.lock",
|
25
|
+
"LICENSE.txt",
|
26
|
+
"README.rdoc",
|
27
|
+
"Rakefile",
|
28
|
+
"bin/blitz",
|
29
|
+
"blitz.gemspec",
|
30
|
+
"lib/blitz.rb",
|
31
|
+
"lib/blitz/client.rb",
|
32
|
+
"lib/blitz/command.rb",
|
33
|
+
"lib/blitz/command/api.rb",
|
34
|
+
"lib/blitz/command/curl.rb",
|
35
|
+
"lib/blitz/command/help.rb",
|
36
|
+
"lib/blitz/curl/error.rb",
|
37
|
+
"lib/blitz/curl/sprint.rb",
|
38
|
+
"lib/blitz/helper.rb",
|
39
|
+
"test/helper.rb",
|
40
|
+
"test/test_blitz.rb"
|
41
|
+
]
|
42
|
+
s.homepage = %q{http://blitz.io}
|
43
|
+
s.licenses = ["MIT"]
|
44
|
+
s.require_paths = ["lib"]
|
45
|
+
s.rubygems_version = %q{1.3.7}
|
46
|
+
s.summary = %q{Make load and performance testing a fun sport}
|
47
|
+
s.test_files = [
|
48
|
+
"test/helper.rb",
|
49
|
+
"test/test_blitz.rb"
|
50
|
+
]
|
51
|
+
|
52
|
+
if s.respond_to? :specification_version then
|
53
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
54
|
+
s.specification_version = 3
|
55
|
+
|
56
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
57
|
+
s.add_runtime_dependency(%q<rest-client>, ["~> 1.6.1"])
|
58
|
+
s.add_runtime_dependency(%q<json>, ["~> 1.4.6"])
|
59
|
+
s.add_runtime_dependency(%q<json_pure>, ["~> 1.4.6"])
|
60
|
+
s.add_runtime_dependency(%q<hexy>, ["~> 0.1.1"])
|
61
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
62
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<rest-client>, ["~> 1.6.1"])
|
65
|
+
s.add_dependency(%q<json>, ["~> 1.4.6"])
|
66
|
+
s.add_dependency(%q<json_pure>, ["~> 1.4.6"])
|
67
|
+
s.add_dependency(%q<hexy>, ["~> 0.1.1"])
|
68
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
69
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
70
|
+
end
|
71
|
+
else
|
72
|
+
s.add_dependency(%q<rest-client>, ["~> 1.6.1"])
|
73
|
+
s.add_dependency(%q<json>, ["~> 1.4.6"])
|
74
|
+
s.add_dependency(%q<json_pure>, ["~> 1.4.6"])
|
75
|
+
s.add_dependency(%q<hexy>, ["~> 0.1.1"])
|
76
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
77
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
data/lib/blitz.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hexy'
|
3
|
+
|
4
|
+
class Blitz
|
5
|
+
require 'blitz/helper'
|
6
|
+
Version = "0.1.0".freeze
|
7
|
+
|
8
|
+
extend Blitz::Helper
|
9
|
+
|
10
|
+
def self.run cmd, argv
|
11
|
+
kname, mname = cmd.split(':', 2)
|
12
|
+
klass = Blitz::Command.const_get kname.capitalize rescue nil
|
13
|
+
mname ||= 'default'
|
14
|
+
mname = "cmd_#{mname}".to_sym
|
15
|
+
if klass and klass < Blitz::Command and klass.method_defined? mname
|
16
|
+
command = klass.new
|
17
|
+
begin
|
18
|
+
command.send mname, argv
|
19
|
+
rescue Test::Unit::AssertionFailedError, ArgumentError => e
|
20
|
+
error e.message.chomp('.')
|
21
|
+
end
|
22
|
+
else
|
23
|
+
error "Unknown command #{cmd}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'blitz/client'
|
29
|
+
require 'blitz/curl/error'
|
30
|
+
require 'blitz/curl/sprint'
|
31
|
+
require 'blitz/command'
|
data/lib/blitz/client.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'json/pure'
|
3
|
+
require 'restclient'
|
4
|
+
|
5
|
+
class Blitz
|
6
|
+
class Client
|
7
|
+
attr_reader :blitz
|
8
|
+
|
9
|
+
def initialize user, apik, host='blitz.io'
|
10
|
+
scheme = host.index('localhost') ? 'http' : 'https'
|
11
|
+
@blitz = RestClient::Resource.new "#{scheme}://#{host}", \
|
12
|
+
:headers => {
|
13
|
+
:x_api_user => user,
|
14
|
+
:x_api_key => apik,
|
15
|
+
:x_gem_version => ::Blitz::Version
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def curl_execute data
|
20
|
+
JSON.parse blitz['/api/1/curl/execute'].post(data.to_json)
|
21
|
+
end
|
22
|
+
|
23
|
+
def login
|
24
|
+
JSON.parse blitz['/login/api'].get
|
25
|
+
end
|
26
|
+
|
27
|
+
def job_status job_id
|
28
|
+
JSON.parse blitz["/api/1/jobs/#{job_id}/status"].get
|
29
|
+
end
|
30
|
+
|
31
|
+
def abort_job job_id
|
32
|
+
JSON.parse blitz["/api/1/jobs/#{job_id}/abort"].put ''
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'test/unit/assertions'
|
2
|
+
|
3
|
+
# The default template string contains what was sent and received. Strip
|
4
|
+
# these out since we don't need them
|
5
|
+
class Test::Unit::Assertions::AssertionMessage
|
6
|
+
alias :old_template :template
|
7
|
+
|
8
|
+
def template
|
9
|
+
@template_string = ''
|
10
|
+
@parameters = []
|
11
|
+
old_template
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Blitz
|
16
|
+
class Command
|
17
|
+
include Test::Unit::Assertions
|
18
|
+
include Helper
|
19
|
+
end
|
20
|
+
end # Blitz
|
21
|
+
|
22
|
+
Dir["#{File.dirname(__FILE__)}/command/*.rb"].each { |c| require c }
|
@@ -0,0 +1,96 @@
|
|
1
|
+
class Blitz
|
2
|
+
class Command
|
3
|
+
class API < Command
|
4
|
+
attr_accessor :credentials
|
5
|
+
|
6
|
+
def cmd_init argv
|
7
|
+
FileUtils.rm credentials_file rescue nil
|
8
|
+
API.client
|
9
|
+
|
10
|
+
msg "You are now ready to blitz!"
|
11
|
+
msg "Try blitz help to learn more about the commands."
|
12
|
+
end
|
13
|
+
|
14
|
+
def client
|
15
|
+
get_credentials
|
16
|
+
Blitz::Client.new(user, password, host)
|
17
|
+
end
|
18
|
+
|
19
|
+
def user
|
20
|
+
get_credentials
|
21
|
+
@credentials[0]
|
22
|
+
end
|
23
|
+
|
24
|
+
def password
|
25
|
+
get_credentials
|
26
|
+
@credentials[1]
|
27
|
+
end
|
28
|
+
|
29
|
+
def host
|
30
|
+
ENV['BLITZ_HOST'] || 'blitz.io'
|
31
|
+
end
|
32
|
+
|
33
|
+
def credentials_file
|
34
|
+
ENV['HOME'] + '/.blitz/credentials'
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_credentials
|
38
|
+
return if @credentials
|
39
|
+
unless @credentials = read_credentials
|
40
|
+
@credentials = ask_for_credentials
|
41
|
+
save_credentials
|
42
|
+
end
|
43
|
+
@credentials
|
44
|
+
end
|
45
|
+
|
46
|
+
def read_credentials
|
47
|
+
File.exists?(credentials_file) and File.read(credentials_file).split("\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
def ask_for_credentials
|
51
|
+
msg "Enter your blitz credentials."
|
52
|
+
print "User-ID: "
|
53
|
+
user = ask
|
54
|
+
print "API-Key: "
|
55
|
+
apik = ask
|
56
|
+
apik2 = Blitz::Client.new(user, apik, host).login['api_key']
|
57
|
+
if not apik2
|
58
|
+
error "Authentication failed"
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
|
62
|
+
[ user, apik2 ]
|
63
|
+
end
|
64
|
+
|
65
|
+
def save_credentials
|
66
|
+
write_credentials
|
67
|
+
end
|
68
|
+
|
69
|
+
def write_credentials
|
70
|
+
FileUtils.mkdir_p(File.dirname(credentials_file))
|
71
|
+
File.open(credentials_file, 'w') do |f|
|
72
|
+
f.puts self.credentials
|
73
|
+
end
|
74
|
+
set_credentials_permissions
|
75
|
+
end
|
76
|
+
|
77
|
+
def set_credentials_permissions
|
78
|
+
FileUtils.chmod 0700, File.dirname(credentials_file)
|
79
|
+
FileUtils.chmod 0600, credentials_file
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.instance
|
83
|
+
@instance ||= API.new
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.client
|
87
|
+
self.instance.client
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
def initialize
|
92
|
+
end
|
93
|
+
end
|
94
|
+
Api = API
|
95
|
+
end # Command
|
96
|
+
end # Blitz
|
@@ -0,0 +1,272 @@
|
|
1
|
+
class Blitz
|
2
|
+
class Command
|
3
|
+
class Curl < Command
|
4
|
+
def cmd_help argv
|
5
|
+
help
|
6
|
+
end
|
7
|
+
|
8
|
+
def cmd_run argv
|
9
|
+
args = parse_cli argv
|
10
|
+
if args['help']
|
11
|
+
return help
|
12
|
+
end
|
13
|
+
|
14
|
+
if not args['pattern']
|
15
|
+
sprint args
|
16
|
+
return
|
17
|
+
else
|
18
|
+
error "Rushing not supported yet, but coming soon"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :cmd_default, :cmd_run
|
23
|
+
|
24
|
+
private
|
25
|
+
def authorize_error e
|
26
|
+
base_url = "#{e.scheme}://#{e.host}:#{e.port}"
|
27
|
+
puts
|
28
|
+
error "You haven't verified that you are the devops dude for #{e.host}"
|
29
|
+
error ""
|
30
|
+
error "Make sure the following URL is reachable by us and returns the value 42."
|
31
|
+
error "#{base_url}/#{e.uuid}"
|
32
|
+
error ""
|
33
|
+
error "If your app is RESTfully built with sinatra or rails, simply"
|
34
|
+
error "add a route like so:"
|
35
|
+
error ""
|
36
|
+
error "get '/#{e.uuid}' do"
|
37
|
+
error " '42'"
|
38
|
+
error "end"
|
39
|
+
error ""
|
40
|
+
error "And we'll be on our merry way to blitz #{e.host}"
|
41
|
+
puts
|
42
|
+
end
|
43
|
+
|
44
|
+
def sprint args
|
45
|
+
begin
|
46
|
+
job = ::Blitz::Curl::Sprint.execute args
|
47
|
+
result = job.result
|
48
|
+
print_sprint_result args, result
|
49
|
+
rescue ::Blitz::Curl::Error::Authorize => e
|
50
|
+
authorize_error e
|
51
|
+
rescue ::Blitz::Curl::Error::Region => e
|
52
|
+
error "#{e.region}: #{e.message}"
|
53
|
+
rescue ::Blitz::Curl::Error => e
|
54
|
+
error e.message
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def print_sprint_result args, result
|
59
|
+
rtt = result.duration
|
60
|
+
if rtt < 1.0
|
61
|
+
rtt = (rtt * 1000).floor.to_s + ' ms';
|
62
|
+
else
|
63
|
+
rtt = ("%.2f" % rtt) + ' sec';
|
64
|
+
end
|
65
|
+
|
66
|
+
puts "-" * 70
|
67
|
+
msg "#{result.region}: Response time of #{rtt}"
|
68
|
+
unless args['dump-header'] or args['verbose']
|
69
|
+
msg "Try --verbose to see the request/response headers"
|
70
|
+
end
|
71
|
+
puts "-" * 70
|
72
|
+
puts
|
73
|
+
|
74
|
+
if args['dump-header'] or args['verbose']
|
75
|
+
puts "> " + result.request.line
|
76
|
+
result.request.headers.each_pair { |k, v| puts "> #{k}: #{v}\r\n" }
|
77
|
+
puts
|
78
|
+
|
79
|
+
content = result.request.content
|
80
|
+
if not content.empty?
|
81
|
+
if /^[[:print:]]+$/ =~ content
|
82
|
+
puts content
|
83
|
+
else
|
84
|
+
puts Hexy.new(content).to_s
|
85
|
+
end
|
86
|
+
puts
|
87
|
+
end
|
88
|
+
|
89
|
+
puts "< " + result.response.line
|
90
|
+
result.response.headers.each_pair { |k, v| puts "> #{k}: #{v}\r\n" }
|
91
|
+
puts
|
92
|
+
end
|
93
|
+
|
94
|
+
content = result.response.content
|
95
|
+
if not content.empty?
|
96
|
+
if /^[[:print:]]+$/ =~ content
|
97
|
+
puts content
|
98
|
+
else
|
99
|
+
puts Hexy.new(content).to_s
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def help
|
105
|
+
helps = [
|
106
|
+
{ :short => '-A', :long => '--user-agent', :value => '<string>', :help => 'User-Agent to send to server' },
|
107
|
+
{ :short => '-b', :long => '--cookie', :value => 'name=<string>', :help => 'Cookie to send to the server (multiple)' },
|
108
|
+
{ :short => '-d', :long => '--data', :value => '<string>', :help => 'Data to send in a PUT or POST request' },
|
109
|
+
{ :short => '-D', :long => '--dump-header', :value => '<file>', :help => 'Print the request/response headers' },
|
110
|
+
{ :short => '-e', :long => '--referer', :value => '<string>', :help => 'Referer URL' },
|
111
|
+
{ :short => '-h', :long => '--help', :value => '', :help => 'Help on command line options' },
|
112
|
+
{ :short => '-H', :long => '--header', :value => '<string>', :help => 'Custom header to pass to server' },
|
113
|
+
{ :short => '-p', :long => '--pattern', :value => '<s>-<e>:<d>', :help => 'Ramp from s to e concurrent requests in d secs' },
|
114
|
+
{ :short => '-r', :long => '--region', :value => '<string>', :help => 'Choose one of california, virginia, singapore or ireland' },
|
115
|
+
{ :short => '-s', :long => '--status', :value => '<number>', :help => 'Assert on the HTTP response status code' },
|
116
|
+
{ :short => '-T', :long => '--timeout', :value => '<ms>', :help => 'Wait time for both connect and responses' },
|
117
|
+
{ :short => '-u', :long => '--user', :value => '<user[:pass]>', :help => 'User and password for authentication' },
|
118
|
+
{ :short => '-X', :long => '--request', :value => '<string>', :help => 'Request method to use (GET, HEAD, PUT, etc.)' },
|
119
|
+
{ :short => '-v', :long => '--verbose', :value => '', :help => 'Print the request/response headers' },
|
120
|
+
{ :short => '-1', :long => '--tlsv1', :value => '', :help => 'Use TLSv1 (SSL)' },
|
121
|
+
{ :short => '-2', :long => '--sslv2', :value => '', :help => 'Use SSLv2 (SSL)' },
|
122
|
+
{ :short => '-3', :long => '--sslv3', :value => '', :help => 'Use SSLv3 (SSL)' }
|
123
|
+
]
|
124
|
+
|
125
|
+
max_long_size = helps.inject(0) { |memo, obj| [ obj[:long].size, memo ].max }
|
126
|
+
max_value_size = helps.inject(0) { |memo, obj| [ obj[:value].size, memo ].max }
|
127
|
+
puts
|
128
|
+
msg "Usage: blitz curl <options> <url>"
|
129
|
+
puts
|
130
|
+
helps.each do |h|
|
131
|
+
msg "%-*s %*s %-*s %s" % [max_long_size, h[:long], 2, h[:short], max_value_size, h[:value], h[:help]]
|
132
|
+
end
|
133
|
+
puts
|
134
|
+
end
|
135
|
+
|
136
|
+
def parse_cli argv
|
137
|
+
hash = Hash.new
|
138
|
+
while not argv.empty?
|
139
|
+
break if argv.first[0,1] != '-'
|
140
|
+
|
141
|
+
k = argv.shift
|
142
|
+
if [ '-A', '--user-agent' ].member? k
|
143
|
+
hash['user-agent'] = shift(k, argv)
|
144
|
+
next
|
145
|
+
end
|
146
|
+
|
147
|
+
if [ '-b', '--cookie' ].member? k
|
148
|
+
# TODO: support cookie jars
|
149
|
+
hash['cookies'] ||= []
|
150
|
+
hash['cookies'] << shift(k, argv)
|
151
|
+
next
|
152
|
+
end
|
153
|
+
|
154
|
+
if [ '-d', '--data' ].member? k
|
155
|
+
hash['content'] ||= Hash.new
|
156
|
+
hash['content']['data'] ||= []
|
157
|
+
v = shift(k, argv)
|
158
|
+
v = File.read v[1..-1] if v =~ /^@/
|
159
|
+
hash['content']['data'] << v
|
160
|
+
next
|
161
|
+
end
|
162
|
+
|
163
|
+
if [ '-D', '--dump-header' ].member? k
|
164
|
+
hash['dump-header'] = shift(k, argv)
|
165
|
+
next
|
166
|
+
end
|
167
|
+
|
168
|
+
if [ '-e', '--referer'].member? k
|
169
|
+
hash['referer'] = shift(k, argv)
|
170
|
+
next
|
171
|
+
end
|
172
|
+
|
173
|
+
if [ '-h', '--help' ].member? k
|
174
|
+
hash['help'] = true
|
175
|
+
next
|
176
|
+
end
|
177
|
+
|
178
|
+
if [ '-H', '--header' ].member? k
|
179
|
+
hash['headers'] ||= []
|
180
|
+
hash['headers'].push shift(k, argv)
|
181
|
+
next
|
182
|
+
end
|
183
|
+
|
184
|
+
if [ '-p', '--pattern' ].member? k
|
185
|
+
v = shift(k, argv)
|
186
|
+
if not /^(\d+)-(\d+):(\d+)$/ =~ v
|
187
|
+
raise Test::Unit::AssertionFailedError, "invalid ramp pattern"
|
188
|
+
end
|
189
|
+
hash['pattern'] = {
|
190
|
+
'iterations' => 1,
|
191
|
+
'intervals' => [{
|
192
|
+
'iterations' => 1,
|
193
|
+
'start' => $1.to_i,
|
194
|
+
'end' => $2.to_i,
|
195
|
+
'duration' => $3.to_i
|
196
|
+
}]
|
197
|
+
}
|
198
|
+
next
|
199
|
+
end
|
200
|
+
|
201
|
+
if [ '-r', '--region' ].member? k
|
202
|
+
v = shift(k, argv)
|
203
|
+
assert_match(/^california|virginia|singapore|ireland|japan$/, v, 'region must be one of california, virginia, singapore, japan or ireland')
|
204
|
+
hash['region'] = v
|
205
|
+
next
|
206
|
+
end
|
207
|
+
|
208
|
+
if [ '-s', '--status' ].member? k
|
209
|
+
hash['status'] = shift(k, argv).to_i
|
210
|
+
next
|
211
|
+
end
|
212
|
+
|
213
|
+
if [ '-T', '--timeout' ].member? k
|
214
|
+
hash['timeout'] = shift(k, argv).to_i
|
215
|
+
next
|
216
|
+
end
|
217
|
+
|
218
|
+
if [ '-u', '--user' ].member? k
|
219
|
+
hash['user'] = shift(k, argv)
|
220
|
+
next
|
221
|
+
end
|
222
|
+
|
223
|
+
if [ '-X', '--request' ].member? k
|
224
|
+
hash['request'] = shift(k, argv)
|
225
|
+
next
|
226
|
+
end
|
227
|
+
|
228
|
+
if [ '-v', '--verbose' ].member? k
|
229
|
+
hash['verbose'] = true
|
230
|
+
next
|
231
|
+
end
|
232
|
+
|
233
|
+
if [ '-1', '--tlsv1' ].member? k
|
234
|
+
hash['ssl'] = 'tlsv1'
|
235
|
+
next
|
236
|
+
end
|
237
|
+
|
238
|
+
if [ '-2', '--sslv2' ].member? k
|
239
|
+
hash['ssl'] = 'sslv2'
|
240
|
+
next
|
241
|
+
end
|
242
|
+
|
243
|
+
if [ '-3', '--sslv3' ].member? k
|
244
|
+
hash['ssl'] = 'sslv2'
|
245
|
+
next
|
246
|
+
end
|
247
|
+
|
248
|
+
raise ArgumentError, "Unknown option #{k}"
|
249
|
+
end
|
250
|
+
|
251
|
+
if not hash['help']
|
252
|
+
url = argv.shift
|
253
|
+
assert_not_nil(url, 'no URL specified!')
|
254
|
+
hash['url'] = url
|
255
|
+
end
|
256
|
+
|
257
|
+
if hash.member? 'content'
|
258
|
+
data_size = hash['content']['data'].inject(0) { |m, v| m + v.size }
|
259
|
+
assert(data_size < 10*1024, "POST content must be < 10K")
|
260
|
+
end
|
261
|
+
|
262
|
+
hash
|
263
|
+
end
|
264
|
+
|
265
|
+
def shift key, argv
|
266
|
+
val = argv.shift
|
267
|
+
assert_not_nil(val, "missing value for #{key}")
|
268
|
+
val
|
269
|
+
end
|
270
|
+
end # Curl
|
271
|
+
end # Command
|
272
|
+
end # Blitz
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Blitz
|
2
|
+
class Command
|
3
|
+
class Help < Command
|
4
|
+
def cmd_default argv
|
5
|
+
puts
|
6
|
+
msg "Usage: blitz <command> <options>"
|
7
|
+
helps = [
|
8
|
+
{ :cmd => 'help', :help => "Display this help" },
|
9
|
+
{ :cmd => 'api:init', :help => 'Validate your API key' },
|
10
|
+
{ :cmd => 'curl', :help => 'Run a sprint or a rush' },
|
11
|
+
{ :cmd => 'curl:help', :help => 'Show help on sprint and rushing' }
|
12
|
+
]
|
13
|
+
|
14
|
+
max_cmd_size = helps.inject(0) { |memo, obj| [ obj[:cmd].size, memo ].max } + 4
|
15
|
+
helps.each do |h|
|
16
|
+
msg "%*s - %s" % [max_cmd_size, h[:cmd], h[:help]]
|
17
|
+
end
|
18
|
+
puts
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end # Command
|
22
|
+
end # Blitz
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class Blitz
|
2
|
+
module Curl
|
3
|
+
class Error < StandardError
|
4
|
+
def initialize json={}
|
5
|
+
super json['reason'] || "Hmmm, something went wrong. Try again in a little bit?"
|
6
|
+
end
|
7
|
+
|
8
|
+
class Authorize < Error
|
9
|
+
attr_reader :scheme, :host, :port, :uuid
|
10
|
+
|
11
|
+
def initialize json
|
12
|
+
@scheme = json['scheme']
|
13
|
+
@host = json['host']
|
14
|
+
@port = json['port']
|
15
|
+
@uuid = json['uuid']
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Region < Error
|
21
|
+
attr_reader :region
|
22
|
+
|
23
|
+
def initialize json
|
24
|
+
@region = json['region']
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class DNS < Region
|
30
|
+
end
|
31
|
+
|
32
|
+
class Connect < Region
|
33
|
+
end
|
34
|
+
|
35
|
+
class Timeout < Region
|
36
|
+
end
|
37
|
+
|
38
|
+
class Parse < Region
|
39
|
+
end
|
40
|
+
|
41
|
+
class Status < Region
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end # Curl
|
45
|
+
end # Blitz
|
@@ -0,0 +1,108 @@
|
|
1
|
+
class Blitz
|
2
|
+
module Curl
|
3
|
+
class Sprint
|
4
|
+
class Request
|
5
|
+
attr_reader :line
|
6
|
+
attr_reader :method
|
7
|
+
attr_reader :url
|
8
|
+
attr_reader :headers
|
9
|
+
attr_reader :content
|
10
|
+
|
11
|
+
def initialize json
|
12
|
+
@line = json['line']
|
13
|
+
@method = json['method']
|
14
|
+
@url = json['url']
|
15
|
+
@content = json['content'].unpack('m')[0]
|
16
|
+
@headers = json['headers']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Response
|
21
|
+
attr_reader :line
|
22
|
+
attr_reader :status
|
23
|
+
attr_reader :message
|
24
|
+
attr_reader :headers
|
25
|
+
attr_reader :content
|
26
|
+
|
27
|
+
def initialize json
|
28
|
+
@line = json['line']
|
29
|
+
@status = json['status']
|
30
|
+
@message = json['message']
|
31
|
+
@content = json['content'].unpack('m')[0]
|
32
|
+
@headers = json['headers']
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Result
|
37
|
+
attr_reader :region
|
38
|
+
attr_reader :duration
|
39
|
+
attr_reader :connect
|
40
|
+
attr_reader :request
|
41
|
+
attr_reader :response
|
42
|
+
|
43
|
+
def initialize json
|
44
|
+
result = json['result']
|
45
|
+
@region = result['region']
|
46
|
+
@duration = result['duration']
|
47
|
+
@connect = result['connect']
|
48
|
+
@request = Request.new result['request']
|
49
|
+
@response = Response.new result['response']
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.execute args
|
54
|
+
args.delete 'pattern'
|
55
|
+
|
56
|
+
res = Command::API.client.curl_execute args
|
57
|
+
raise Error.new(res) if res['error']
|
58
|
+
return self.new res['job_id']
|
59
|
+
end
|
60
|
+
|
61
|
+
attr_reader :job_id
|
62
|
+
|
63
|
+
def initialize job_id
|
64
|
+
@job_id = job_id
|
65
|
+
end
|
66
|
+
|
67
|
+
def result
|
68
|
+
while true
|
69
|
+
sleep 2.0
|
70
|
+
|
71
|
+
job = Command::API.client.job_status job_id
|
72
|
+
if job['error']
|
73
|
+
raise Error
|
74
|
+
end
|
75
|
+
|
76
|
+
result = job['result']
|
77
|
+
next if job['status'] == 'queued'
|
78
|
+
next if job['status'] == 'running' and not result
|
79
|
+
|
80
|
+
raise Error if not result
|
81
|
+
|
82
|
+
error = result['error']
|
83
|
+
if error
|
84
|
+
if error == 'dns'
|
85
|
+
raise Error::DNS.new(result)
|
86
|
+
elsif error == 'connect'
|
87
|
+
raise Error::Connect.new(result)
|
88
|
+
elsif error == 'timeout'
|
89
|
+
raise Error::Timeout.new(result)
|
90
|
+
elsif error == 'parse'
|
91
|
+
raise Error::Parse.new(result)
|
92
|
+
elsif result['assert'] == 0
|
93
|
+
raise Error::Status.new(result)
|
94
|
+
else
|
95
|
+
raise Error
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
return Result.new(job)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def abort
|
104
|
+
Command::API.client.abort_job job_id rescue nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end # Curl
|
108
|
+
end # Blitz
|
data/lib/blitz/helper.rb
ADDED
data/test/helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
|
12
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
require 'blitz'
|
15
|
+
|
16
|
+
class Test::Unit::TestCase
|
17
|
+
end
|
data/test/test_blitz.rb
ADDED
metadata
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blitz
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- pcapr
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-03-10 00:00:00 -08:00
|
19
|
+
default_executable: blitz
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
name: rest-client
|
25
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 13
|
31
|
+
segments:
|
32
|
+
- 1
|
33
|
+
- 6
|
34
|
+
- 1
|
35
|
+
version: 1.6.1
|
36
|
+
requirement: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
name: json
|
41
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 11
|
47
|
+
segments:
|
48
|
+
- 1
|
49
|
+
- 4
|
50
|
+
- 6
|
51
|
+
version: 1.4.6
|
52
|
+
requirement: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
name: json_pure
|
57
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 11
|
63
|
+
segments:
|
64
|
+
- 1
|
65
|
+
- 4
|
66
|
+
- 6
|
67
|
+
version: 1.4.6
|
68
|
+
requirement: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
name: hexy
|
73
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ~>
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
hash: 25
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
- 1
|
82
|
+
- 1
|
83
|
+
version: 0.1.1
|
84
|
+
requirement: *id004
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
name: bundler
|
89
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ~>
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
hash: 23
|
95
|
+
segments:
|
96
|
+
- 1
|
97
|
+
- 0
|
98
|
+
- 0
|
99
|
+
version: 1.0.0
|
100
|
+
requirement: *id005
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
name: jeweler
|
105
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
hash: 1
|
111
|
+
segments:
|
112
|
+
- 1
|
113
|
+
- 5
|
114
|
+
- 1
|
115
|
+
version: 1.5.1
|
116
|
+
requirement: *id006
|
117
|
+
description: Make load and performance testing a fun sport
|
118
|
+
email: kowsik@gmail.com
|
119
|
+
executables:
|
120
|
+
- blitz
|
121
|
+
extensions: []
|
122
|
+
|
123
|
+
extra_rdoc_files:
|
124
|
+
- LICENSE.txt
|
125
|
+
- README.rdoc
|
126
|
+
files:
|
127
|
+
- .document
|
128
|
+
- Gemfile
|
129
|
+
- Gemfile.lock
|
130
|
+
- LICENSE.txt
|
131
|
+
- README.rdoc
|
132
|
+
- Rakefile
|
133
|
+
- bin/blitz
|
134
|
+
- blitz.gemspec
|
135
|
+
- lib/blitz.rb
|
136
|
+
- lib/blitz/client.rb
|
137
|
+
- lib/blitz/command.rb
|
138
|
+
- lib/blitz/command/api.rb
|
139
|
+
- lib/blitz/command/curl.rb
|
140
|
+
- lib/blitz/command/help.rb
|
141
|
+
- lib/blitz/curl/error.rb
|
142
|
+
- lib/blitz/curl/sprint.rb
|
143
|
+
- lib/blitz/helper.rb
|
144
|
+
- test/helper.rb
|
145
|
+
- test/test_blitz.rb
|
146
|
+
has_rdoc: true
|
147
|
+
homepage: http://blitz.io
|
148
|
+
licenses:
|
149
|
+
- MIT
|
150
|
+
post_install_message:
|
151
|
+
rdoc_options: []
|
152
|
+
|
153
|
+
require_paths:
|
154
|
+
- lib
|
155
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
156
|
+
none: false
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
hash: 3
|
161
|
+
segments:
|
162
|
+
- 0
|
163
|
+
version: "0"
|
164
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
|
+
none: false
|
166
|
+
requirements:
|
167
|
+
- - ">="
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
hash: 3
|
170
|
+
segments:
|
171
|
+
- 0
|
172
|
+
version: "0"
|
173
|
+
requirements: []
|
174
|
+
|
175
|
+
rubyforge_project:
|
176
|
+
rubygems_version: 1.3.7
|
177
|
+
signing_key:
|
178
|
+
specification_version: 3
|
179
|
+
summary: Make load and performance testing a fun sport
|
180
|
+
test_files:
|
181
|
+
- test/helper.rb
|
182
|
+
- test/test_blitz.rb
|