hardy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +21 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +173 -0
- data/Rakefile +1 -0
- data/bin/hardy +156 -0
- data/hardy.gemspec +24 -0
- data/lib/hardy/version.rb +3 -0
- metadata +120 -0
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Nathaniel Bibler
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
# Hardy
|
2
|
+
|
3
|
+
Hardy is a [Thor][thor] library which easily converts an [HTTP Archive
|
4
|
+
(HAR)][har] file into a [siege][siege] URLs file.
|
5
|
+
|
6
|
+
What does that mean for you? Well, it means that it's now a trivial task to
|
7
|
+
generate load testing scripts for your HTTP(S) web applications and determine
|
8
|
+
exactly how many concurrent, active, hammering-away-on-your-systems users your
|
9
|
+
application and infrastructure can fully support.
|
10
|
+
|
11
|
+
Stop guessing and find out! It's easy!!
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile (probably not in the default group,
|
16
|
+
but more likely in a :test or :development group):
|
17
|
+
|
18
|
+
gem 'hardy'
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
$ gem install hardy
|
27
|
+
|
28
|
+
### Generating HAR files
|
29
|
+
|
30
|
+
Creating a HAR file is simple:
|
31
|
+
|
32
|
+
1. Open Chrome,
|
33
|
+
2. Open the Chrome Developer Tools panel (cmd-shift-i on a Mac),
|
34
|
+
3. With the tools panel open, navigate to the site you want to test,
|
35
|
+
4. Click the Preserve Log upon Navigation button on the Network tab of the
|
36
|
+
tools panel (otherwise, your Network tab will be cleared with each page
|
37
|
+
view),
|
38
|
+
5. Click around the site! Act like the user you want to simulate. Meaning,
|
39
|
+
click links, post forms, change pages, sign in, sign out. Whatever you do
|
40
|
+
here will be recreated by your load test users, so try to do things that
|
41
|
+
will generate interesting test metrics.
|
42
|
+
6. Right-click in the Network panel and click "Save as HAR with Content"
|
43
|
+
|
44
|
+
You now have a HAR file to source for load testing! :beer:
|
45
|
+
|
46
|
+
### Installing siege
|
47
|
+
|
48
|
+
Note: If you are planning on testing a site which uses a JSON interface, you'll
|
49
|
+
want to read the [Enabling JSON support in
|
50
|
+
siege](#enabling-json-support-in-siege) section further down this README.
|
51
|
+
|
52
|
+
On a Mac, you can install siege with homebrew:
|
53
|
+
|
54
|
+
$ brew install siege
|
55
|
+
|
56
|
+
On Ubuntu:
|
57
|
+
|
58
|
+
$ apt-get install siege
|
59
|
+
|
60
|
+
Installing from source:
|
61
|
+
|
62
|
+
$ curl http://www.joedog.org/pub/siege/siege-latest.tar.gz -o siege-latest.tar.gz
|
63
|
+
$ tar xvfz siege-latest.tar.gz
|
64
|
+
$ cd siege-*
|
65
|
+
$ ./configure
|
66
|
+
$ make
|
67
|
+
$ make install
|
68
|
+
|
69
|
+
It is important to note that if you want to load test an HTTPS (SSL) site,
|
70
|
+
you'll need to install siege with SSL support. The Mac homebrew installation
|
71
|
+
and Ubuntu package already enable this, by default.
|
72
|
+
|
73
|
+
$ ./configure --with-ssl
|
74
|
+
|
75
|
+
### Enabling JSON support in siege
|
76
|
+
|
77
|
+
So, you're load testing a web application which uses JSON, huh? Does it look
|
78
|
+
like your application is not recognizing the parameters as JSON? Yeah... as of
|
79
|
+
siege 2.74 (currently the latest version), siege does not recognize `.json`
|
80
|
+
files, nor does it support automatically setting the `Content-Type:
|
81
|
+
application/json` request header. Bummer.
|
82
|
+
|
83
|
+
But you need JSON support, you say? Yeah, me too. Sadly, that means you get to
|
84
|
+
edit some C source code. So, download the latest source (as instructed in the
|
85
|
+
[Installing siege](#installing-siege) section, above) and before you
|
86
|
+
`./configure`, you'll need to edit `src/load.c`:
|
87
|
+
|
88
|
+
```diff
|
89
|
+
static const struct ContentType tmap[] = {
|
90
|
+
{"default", TRUE, "application/x-www-form-urlencoded"},
|
91
|
+
{"ai", FALSE, "application/postscript"},
|
92
|
+
...
|
93
|
+
+ {"json", FALSE, "application/json"},
|
94
|
+
...
|
95
|
+
{"xyz", FALSE, "chemical/x-pdb"},
|
96
|
+
{"zip", FALSE, "application/zip"}
|
97
|
+
};
|
98
|
+
```
|
99
|
+
|
100
|
+
Just add an entry to allow `.json` files to be recognized and transmitted as
|
101
|
+
"application/json" format. Once that's done, re-configure, build, and install
|
102
|
+
siege, as [detailed above](#installing-siege). Now, when the URLs file defines
|
103
|
+
a `.json` file, siege will automatically recognize it and make the request with
|
104
|
+
the proper `Content-Type` request header.
|
105
|
+
|
106
|
+
## Usage
|
107
|
+
|
108
|
+
Once you've created or otherwise acquired your HAR file, use Hardy to convert
|
109
|
+
it to a [siege URLs file][urls-file]:
|
110
|
+
|
111
|
+
$ hardy convert my-har-file.env
|
112
|
+
|
113
|
+
By default, this will create a `urls.siege` file and a `data` directory full of
|
114
|
+
data files to support it.
|
115
|
+
|
116
|
+
### Hardy options
|
117
|
+
|
118
|
+
There are a handful of command line options when running the convert task,
|
119
|
+
ranging from defining your output file to changing the data directory, and
|
120
|
+
more. Check out `hardy help convert` for more details. Below are a few details
|
121
|
+
of the more interesting ones:
|
122
|
+
|
123
|
+
`--host-filter` will restrict the generated URLs file to only include requests to
|
124
|
+
the given host. This is very useful if your site uses a CDN for assets and you
|
125
|
+
do not want to include those requests in your load test (that would be a bad
|
126
|
+
idea!).
|
127
|
+
|
128
|
+
`--host` will replace the request hosts for all generated URLs with the host
|
129
|
+
given. This is useful if you're generating your HAR script on one system (say,
|
130
|
+
your staging server) and want to run the siege test against another (perhaps
|
131
|
+
your production stack).
|
132
|
+
|
133
|
+
`--protocol` will replace the request protocols for all generated URLs with the
|
134
|
+
protocol given. This is useful for creating one script where you're load
|
135
|
+
testing `https://` and another where you're load testing `http://`. Again,
|
136
|
+
commonly useful when you're generating your HAR from a system different from
|
137
|
+
the on you're running your siege against.
|
138
|
+
|
139
|
+
An example where we've generated a `my-site.env` HAR file by walking our
|
140
|
+
staging site (http://staging.my-site.com) and we want to have siege run against
|
141
|
+
our production site (https://www.my-site.com):
|
142
|
+
|
143
|
+
```
|
144
|
+
$ hardy my-site.env --host-filter=staging.my-site.com --host=www.my-site.com --protocol=https://
|
145
|
+
```
|
146
|
+
|
147
|
+
### Using siege
|
148
|
+
|
149
|
+
Now, to use siege with your shiny new URLs file (and data directory), here's an
|
150
|
+
example:
|
151
|
+
|
152
|
+
$ siege -c5 -d10 -t5M -v -f urls.siege
|
153
|
+
|
154
|
+
This tells siege to run with 5 concurrent users (-c5), ramping them up over 10
|
155
|
+
seconds (-d10), running with all 5 users active for 5 minutes (-t5M),
|
156
|
+
displaying the request results to the screen (-v), and sourcing your URLs file
|
157
|
+
for their run script (-f urls.siege).
|
158
|
+
|
159
|
+
siege offers a lot of settings options, so check out `siege --help` for more
|
160
|
+
information.
|
161
|
+
|
162
|
+
## Contributing
|
163
|
+
|
164
|
+
1. Fork it
|
165
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
166
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
167
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
168
|
+
5. Create new Pull Request
|
169
|
+
|
170
|
+
[har]: http://www.softwareishard.com/blog/har-12-spec/
|
171
|
+
[siege]: http://www.joedog.org/siege-home/
|
172
|
+
[thor]: https://github.com/wycats/thor
|
173
|
+
[urls-file]: http://www.joedog.org/siege-manual/#a05
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/hardy
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'delegate'
|
4
|
+
require 'digest'
|
5
|
+
|
6
|
+
require 'addressable/uri'
|
7
|
+
require 'mime/types'
|
8
|
+
require 'har'
|
9
|
+
require 'thor'
|
10
|
+
|
11
|
+
class RequestWithURI < BasicObject
|
12
|
+
def initialize(request)
|
13
|
+
@request = request
|
14
|
+
end
|
15
|
+
|
16
|
+
def uri
|
17
|
+
@uri ||= ::Addressable::URI.parse(url)
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method, *args, &block)
|
21
|
+
@request.public_send(method, *args, &block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Hardy
|
26
|
+
class Siege < Thor
|
27
|
+
desc "convert HAR_PATH", "convert a HAR file to a siege URLs file"
|
28
|
+
method_option :data_path, :type => :string, :aliases => '-d', :default => 'data', :desc => 'directory to output request data for POST/PUT requests'
|
29
|
+
method_option :host_filter, :type => :string, :desc => 'filter the HAR content to only a matching host'
|
30
|
+
method_option :force, :type => :boolean, :aliases => '-f', :default => false, :desc => 'override output if it already exists'
|
31
|
+
method_option :host, :type => :string, :aliases => '-h', :desc => 'convert source HAR hosts into a different URL host'
|
32
|
+
method_option :output, :type => :string, :aliases => '-o', :default => 'urls.siege', :desc => 'file path populate with URL content'
|
33
|
+
method_option :protocol, :type => :string, :aliases => '-p', :desc => 'convert source HAR protocol into a different protocol'
|
34
|
+
def convert(har_path)
|
35
|
+
if File.exist?(options[:output])
|
36
|
+
if File.directory?(options[:output])
|
37
|
+
abort("Must specify a filename, #{options[:output].inspect} is a directory")
|
38
|
+
elsif options[:force]
|
39
|
+
FileUtils.rm(options[:output])
|
40
|
+
else
|
41
|
+
abort("Output file already exists, use -f to force.")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
siege_lines = []
|
46
|
+
HAR::Archive.from_file(har_path).entries.each do |entry|
|
47
|
+
siege_lines << create_siege_entry_from(RequestWithURI.new(entry.request))
|
48
|
+
end
|
49
|
+
|
50
|
+
File.open(options[:output], 'w') do |file|
|
51
|
+
file.puts siege_lines.compact.join("\n")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
|
59
|
+
def create_data_file(filename, data)
|
60
|
+
File.open(filename, 'w') do |file|
|
61
|
+
file.write data
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_siege_entry_from(request)
|
66
|
+
if host_filter && request.uri.host.downcase != host_filter.downcase
|
67
|
+
return
|
68
|
+
end
|
69
|
+
|
70
|
+
siege_url_for(request)
|
71
|
+
end
|
72
|
+
|
73
|
+
def data_filename_for(request)
|
74
|
+
post_data = request.post_data.text rescue nil
|
75
|
+
|
76
|
+
if post_data
|
77
|
+
content_type = request.headers.detect { |header| header['name'] == 'Content-Type' } || {'value' => 'text/html'}
|
78
|
+
content_type = content_type['value'].split(';', 2).first
|
79
|
+
mime_type = MIME::Types[content_type].first
|
80
|
+
|
81
|
+
"%{data_path}%{basename}.%{extension}" % {
|
82
|
+
data_path: data_path,
|
83
|
+
basename: Digest::MD5.hexdigest(request.post_data.text),
|
84
|
+
extension: mime_type ? mime_type.extensions.first : 'postdata'
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def data_path
|
90
|
+
if options[:data_path]
|
91
|
+
FileUtils.mkdir(options[:data_path]) unless File.exist?(options[:data_path])
|
92
|
+
options[:data_path] + "/"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def host_filter
|
97
|
+
options[:host_filter]
|
98
|
+
end
|
99
|
+
|
100
|
+
def host_for(uri)
|
101
|
+
options[:host] || uri.host
|
102
|
+
end
|
103
|
+
|
104
|
+
def protocol_for(uri)
|
105
|
+
if protocol = options[:protocol]
|
106
|
+
options[:protocol] =~ /:\/\// ?
|
107
|
+
options[:protocol] :
|
108
|
+
options[:protocol] + "://"
|
109
|
+
else
|
110
|
+
"%s://" % uri.scheme
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def host_for(uri)
|
115
|
+
options[:host] || uri.host
|
116
|
+
end
|
117
|
+
|
118
|
+
def siege_url_for(request)
|
119
|
+
url = url_for(request.uri)
|
120
|
+
|
121
|
+
case request.method
|
122
|
+
when 'GET'
|
123
|
+
url
|
124
|
+
when 'POST', 'PUT'
|
125
|
+
if filename = data_filename_for(request)
|
126
|
+
create_data_file(filename, request.post_data.text)
|
127
|
+
"%{url} %{method} < %{filename}" % {
|
128
|
+
url: url,
|
129
|
+
method: request.method,
|
130
|
+
filename: filename
|
131
|
+
}
|
132
|
+
else
|
133
|
+
"%s POST 1=1" % url
|
134
|
+
end
|
135
|
+
else
|
136
|
+
raise NotImplementedError, "received a #{request.method.inspect} request, unhandled"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def url_for(uri)
|
141
|
+
path = uri.path
|
142
|
+
#path += ".json" unless path =~ /\./ || path[-1] == '/'
|
143
|
+
|
144
|
+
url = "%{proto}%{host}%{path}" % {
|
145
|
+
proto: protocol_for(uri),
|
146
|
+
host: host_for(uri),
|
147
|
+
path: path
|
148
|
+
}
|
149
|
+
|
150
|
+
url += "?#{uri.query}" if uri.query
|
151
|
+
url
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
Hardy::Siege.start
|
data/hardy.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hardy/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "hardy"
|
8
|
+
gem.version = Hardy::VERSION
|
9
|
+
gem.authors = ["Nathaniel Bibler"]
|
10
|
+
gem.email = ["contact@nathanielbibler.com"]
|
11
|
+
gem.description = %q{Convert an HTTP Archive (HAR) into a siege URLs file with Content-Type request support.}
|
12
|
+
gem.summary = %q{Convert HTTP Archive (HAR) files to siege URL files}
|
13
|
+
gem.homepage = "https://github.com/nbibler/hardy"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency 'addressable', '~>2.0'
|
21
|
+
gem.add_dependency 'har'
|
22
|
+
gem.add_dependency 'mime-types', '~>1.0'
|
23
|
+
gem.add_dependency 'thor'
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hardy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nathaniel Bibler
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: addressable
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.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: '2.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: har
|
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
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: mime-types
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: thor
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Convert an HTTP Archive (HAR) into a siege URLs file with Content-Type
|
79
|
+
request support.
|
80
|
+
email:
|
81
|
+
- contact@nathanielbibler.com
|
82
|
+
executables:
|
83
|
+
- hardy
|
84
|
+
extensions: []
|
85
|
+
extra_rdoc_files: []
|
86
|
+
files:
|
87
|
+
- .gitignore
|
88
|
+
- CHANGELOG.md
|
89
|
+
- Gemfile
|
90
|
+
- LICENSE.txt
|
91
|
+
- README.md
|
92
|
+
- Rakefile
|
93
|
+
- bin/hardy
|
94
|
+
- hardy.gemspec
|
95
|
+
- lib/hardy/version.rb
|
96
|
+
homepage: https://github.com/nbibler/hardy
|
97
|
+
licenses: []
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ! '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ! '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 1.8.25
|
117
|
+
signing_key:
|
118
|
+
specification_version: 3
|
119
|
+
summary: Convert HTTP Archive (HAR) files to siege URL files
|
120
|
+
test_files: []
|