blitz 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
## ![blitz.io](http://blitz.io/images/logo2.png)
|
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
|