hardy 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.
@@ -0,0 +1,21 @@
1
+ *.env
2
+ *.siege
3
+ data/
4
+
5
+ *.gem
6
+ *.rbc
7
+ .bundle
8
+ .config
9
+ .yardoc
10
+ Gemfile.lock
11
+ InstalledFiles
12
+ _yardoc
13
+ coverage
14
+ doc/
15
+ lib/bundler/man
16
+ pkg
17
+ rdoc
18
+ spec/reports
19
+ test/tmp
20
+ test/version_tmp
21
+ tmp
@@ -0,0 +1,12 @@
1
+ # hardy changelog
2
+
3
+ ## [HEAD / unreleased][head.diff]
4
+
5
+ No significant changes.
6
+
7
+ ## 0.0.1 / 2013-02-14
8
+
9
+ * Initial release.
10
+
11
+
12
+ [head.diff]: https://github.com/nbibler/hardy/compare/v0.0.1...master
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -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.
@@ -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
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Hardy
2
+ VERSION = "0.0.1"
3
+ 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: []