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.
- 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: []
|