herokubench 0.0.7

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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ Y2I3MWIyOTY5ZjYwMTNiYTk0MjJmOTBhNGFmM2ZiYTRiMWRhZjcxZg==
5
+ data.tar.gz: !binary |-
6
+ OWNiY2M4NWU2MmRlZDMzM2VjYWY3ZjE4NDE3MjgxNzhiMDk2ODhlNg==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NzRiNTIwNTdhYjM3YWFhYmY4MjA0M2Y3MWFmNzkxYzkyODlkN2FkN2M3M2Y4
10
+ ZTIyZTY5YTBjZTNlY2Y1NWVhZDBhMDllY2M1MGI2MWRjMGY1MmMyMjk4YzBh
11
+ NjliZjU0NmVhNDg1NDhkODNiZDcyZmU5Njk0NzA2ZDFkOWNkNDA=
12
+ data.tar.gz: !binary |-
13
+ NTJmNDc4NDU2Y2YxMjJiNGExMTAwNWRiNmIxYjExMjI1YWViM2UzZDM4NTk0
14
+ ZWI1ZjIzMGU2NzkxNGM2NTAyOWFmZTUxNmQ5ZjYzNmNlN2M5OTg3ZjIyOTQ5
15
+ NjhkYzhlZTVkMTZlYTY3Mzc5YzI0NjYxYzM5ODI1YWMxNWYzNzc=
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # Heroku Bench
2
+
3
+ A build server in the cloud, heavily inspired by Vulcan.
4
+
5
+ ## Install
6
+
7
+ $ gem install herokubench
8
+
9
+ ## Usage
10
+
11
+ $ hb help
12
+ ommands:
13
+ hb ab URL # Run apache-bench, using a single, one-off Heroku dyno
14
+ hb create APP_NAME # Create your personal bench-server on Heroku
15
+ hb help [COMMAND] # Describe available commands or one specific command
16
+ hb multi URL # Run apache-bench, using multiple one-off dynos
17
+
18
+ Options:
19
+ [--verbose]
20
+
21
+
22
+
23
+ ## Examples
24
+
25
+ ### Create a Bench Server
26
+ $ hb create hbench-david
27
+ Creating hbench-david... done, stack is cedar
28
+ http://hbench-david.herokuapp.com/ | git@heroku.com:hbench-david.git
29
+ ...
30
+
31
+ ### Bench
32
+
33
+ $ hb http://nodejssimple.herokuapp.com/
34
+ Running one-off dyno, please be patient
35
+ Running `ab -c 1000 -n 10000 http://nodejssimple.herokuapp.com/` attached to terminal... up, run.4045
36
+ This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
37
+ Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
38
+ Licensed to The Apache Software Foundation, http://www.apache.org/
39
+
40
+ Benchmarking nodejssimple.herokuapp.com (be patient)
41
+ Completed 1000 requests
42
+ Completed 2000 requests
43
+ Completed 3000 requests
44
+ Completed 4000 requests
45
+ Completed 5000 requests
46
+ Completed 6000 requests
47
+ Completed 7000 requests
48
+ Completed 8000 requests
49
+ Completed 9000 requests
50
+ Completed 10000 requests
51
+ Finished 10000 requests
52
+
53
+
54
+ Server Software:
55
+ Server Hostname: nodejssimple.herokuapp.com
56
+ Server Port: 80
57
+
58
+ Document Path: /
59
+ Document Length: 12 bytes
60
+
61
+ Concurrency Level: 1000
62
+ Time taken for tests: 9.687 seconds
63
+ Complete requests: 10000
64
+ Failed requests: 0
65
+ Write errors: 0
66
+ Total transferred: 1322840 bytes
67
+ HTML transferred: 120000 bytes
68
+ Requests per second: 1032.32 [#/sec] (mean)
69
+ Time per request: 968.692 [ms] (mean)
70
+ Time per request: 0.969 [ms] (mean, across all concurrent requests)
71
+ Transfer rate: 133.36 [Kbytes/sec] received
72
+
73
+ Connection Times (ms)
74
+ min mean[+/-sd] median max
75
+ Connect: 1 8 12.8 3 70
76
+ Processing: 75 513 742.4 297 5077
77
+ Waiting: 75 512 742.5 296 5077
78
+ Total: 121 520 743.3 301 5087
79
+
80
+ Percentage of the requests served within a certain time (ms)
81
+ 50% 301
82
+ 66% 373
83
+ 75% 403
84
+ 80% 566
85
+ 90% 889
86
+ 95% 1805
87
+ 98% 3967
88
+ 99% 4041
89
+ 100% 5087 (longest request)
data/bin/hb ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+
5
+ require "tmpdir"
6
+ require "uri"
7
+ require "herokubench/cli"
8
+
9
+
10
+ HerokuBench::CLI.start
@@ -0,0 +1,2 @@
1
+ module HerokuBench
2
+ end
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "heroku/command"
4
+ require "heroku/command/base"
5
+ require "heroku/command/help"
6
+ require "heroku/command/apps"
7
+ require "heroku/cli"
8
+ require "heroku/plugin"
9
+
10
+ Heroku.user_agent = "heroku-gem/#{Heroku::VERSION} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
11
+ Heroku::Command.load
12
+ Heroku::Command.run("run", ARGV)
13
+
@@ -0,0 +1,286 @@
1
+ require "digest/sha1"
2
+ require "heroku/auth"
3
+ require "heroku/command"
4
+ require "heroku/command/base"
5
+ require "heroku/command/help"
6
+ require "heroku/command/apps"
7
+ require "heroku/cli"
8
+ require "heroku/plugin"
9
+ require "thor"
10
+ require "tmpdir"
11
+ require "uri"
12
+ require "herokubench"
13
+ require "yaml"
14
+ require "pathname"
15
+ require "stringio"
16
+ require 'tempfile'
17
+ require 'ruby-progressbar'
18
+
19
+
20
+ # This class is based upon Vulcan, and copies heavily.
21
+
22
+ class HerokuBench::CLI < Thor
23
+ class_option "verbose", :type => :boolean
24
+ check_unknown_options! :except => [:ab, :multi]
25
+ default_task :ab
26
+ @@result_type = {
27
+ :connection_times=>/(.+):\s+\s+([\d|\.]+)\s+([\d|\.]+)\s+([\d|\.]+)\s+([\d|\.]+)\s+([\d|\.]+)/,
28
+ :generic_result=> /^([\w\s]+):\s*([\d|\.]+)/,
29
+ :response_time_cdf=>/(\d+%)\s+(\d+)/
30
+ }
31
+ @@summable_fields = ["Complete requests","Failed requests", "Write errors"]
32
+ @@medianable_fields = ["Connect", "Processing", "Waiting", "Total"]
33
+
34
+ class_options["verbose"] = false if class_options["verbose"].nil?
35
+ Heroku.user_agent = "heroku-gem/#{Heroku::VERSION} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
36
+ Heroku::Command.load
37
+
38
+
39
+ desc "create APP_NAME", "Create your personal bench-server on Heroku"
40
+ def create(name="")
41
+ Dir.mktmpdir do |dir|
42
+ Dir.chdir(dir) do
43
+
44
+ args = ["#{name}", "-s","cedar", "--buildpack","https://github.com/wcdolphin/heroku-buildpack-apache.git"]
45
+ args.delete("")
46
+ result = capture { Heroku::Command.run("create", args) }
47
+ name = /\s(.+)\.\.\./.match(result).captures[0]
48
+ puts "Created your personal benchserver: #{name}"
49
+ end
50
+ end
51
+ write_config :app => name, :host => "#{name}.herokuapp.com"
52
+ update
53
+ end
54
+
55
+
56
+ desc "ab [options] [http[s]://]hostname[:port]/path", "Run apache-bench using a single one-off dyno"
57
+ long_desc <<-LONGDESC
58
+ 'hb ab' will run apache-bench, using a one-off dyno on Heroku in order
59
+ to best benchmark the performance of your webservice.
60
+
61
+ For more information, run `hb ab help`
62
+
63
+ > $ hb ab -c 100 -n 1000 http://www.google.com
64
+ LONGDESC
65
+
66
+ def ab(*args)
67
+ error "no app yet, please create first" unless config[:app]
68
+ puts "Running one-off dyno, please be patient"
69
+ Heroku::Command.run("run", ["ab #{args.join(' ')}", "--app", "#{config[:app]}"])
70
+ end
71
+
72
+
73
+ desc "multi NUMBER [options] [http[s]://]hostname[:port]/path", "Run apache-bench, using multiple one-off dynos"
74
+ long_desc <<-LONGDESC
75
+ 'hb multi' will run apache-bench, using multiple one-off dynos in order
76
+ to incrase the throughput of your benchmark.
77
+
78
+ The arguments are identical to that of 'hb ab' with the addition
79
+ of the 'NUMBER' argument, representing the number of one-off dynos
80
+ to execute your benchmark on.
81
+
82
+ > $ hb multi 5 http://www.google.com
83
+ LONGDESC
84
+ def multi(dynos, *args)
85
+ error "no app yet, create first" unless config[:app]
86
+ error "Number of dynos must be an integer greater than 1" unless dynos.to_i >= 1
87
+
88
+ bencher_path = File.expand_path("../bencher.rb",__FILE__)
89
+
90
+ dynos = dynos.to_i
91
+ outputs = []
92
+ pid_map = {}
93
+ results = []
94
+ n = 0
95
+
96
+ ab_command = "ab #{args.join(' ')}"
97
+
98
+ p_bar = ProgressBar.new('Setup', dynos)
99
+
100
+ until n == dynos do
101
+ p_bar.inc
102
+ outputs.push Tempfile.new("hbench_out_#{n}")
103
+ pid = spawn( "ruby #{bencher_path} \"#{ab_command} \" --app #{config[:app]}", :out=>outputs[n].path)
104
+ pid_map[pid] = n
105
+ n += 1
106
+ end
107
+
108
+ p_bar.finish
109
+ p_bar = ProgressBar.new('Benching', dynos)
110
+
111
+ until n == 0 do
112
+ output = outputs[pid_map[Process.wait]]
113
+ last_output = output.read if n ==1
114
+ p_bar.inc
115
+ results.push get_result_hash output
116
+ output.unlink
117
+ n -= 1
118
+ end
119
+
120
+ p_bar.finish
121
+
122
+ if results.last.length ==3
123
+ summarize results
124
+ else
125
+ error last_output
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ # Attemptes to parse a value as a Float or integer, defaulting to the original
132
+ # string if unsuccesful.
133
+ def parse(v)
134
+ ((float = Float(v)) && (float % 1.0 == 0) ? float.to_i : float) rescue v
135
+ end
136
+
137
+ # pretty much the opposite of parse. Returns a string of the best way
138
+ # to represent a float, int or string value
139
+ def unparse(v)
140
+ ((float = v.round(1)) && (float % 1.0 == 0) ? float.to_i.to_s : float.to_s) rescue v.to_s
141
+ end
142
+
143
+ def summarize(results)
144
+ summary = {}
145
+
146
+ results.each do |result|
147
+ result.each do |type, hash|
148
+ summary[type] = {} if summary[type].nil?
149
+ hash.each do |k,v|
150
+ puts k if v.nil?
151
+ summary[type][k] = [0.0] * v.length if summary[type][k].nil?
152
+ v.each_index do |i|
153
+ if not @@summable_fields.index(k).nil?
154
+ summary[type][k][i] += v[i]
155
+ elsif not @@medianable_fields.index(k).nil?
156
+ summary[type][k][i] += v[i] / results.length.to_f
157
+ else
158
+ summary[type][k][i] = v[i]
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ say "\tCumulative results"
166
+ say ""
167
+ summary[:generic_result].each{|k,v| say format(k+":",v, 25)}
168
+
169
+ say ""
170
+ say "\t Connection Times (ms)"
171
+ say format("",["min", "mean", "[+/-sd]" ,"median","max"],15)
172
+ summary[:connection_times].each{|k,v| say format(k+":",v, 15)}
173
+
174
+ say ""
175
+ say "\t Percentage of the requests served within a certain time (ms)"
176
+ say "\t averaged across dynos"
177
+ summary[:response_time_cdf].each{|k,v| say format(k+"",v, 15)}
178
+ end
179
+
180
+ def format(k,v, pad)
181
+ "#{fill(k,pad)}#{v.map{|val| fill(unparse val)}.join('')}\r\n"
182
+ end
183
+
184
+ def fill(str, length=12)
185
+ "#{str}#{" " * (length - str.length)}"
186
+ end
187
+
188
+ def get_result_hash(f)
189
+ result_hash = {}
190
+ f.each_line do |line|
191
+ @@result_type.each do |k,v|
192
+ group = line.scan(v)
193
+ if not group.nil? and group.length.equal? 1
194
+ capture = group[0].map {|v| parse v} #convert to float/int/etc
195
+ result_hash[k] = {} unless result_hash.has_key?(k)
196
+ res_key = capture[0]
197
+ res_values = capture.slice(1, capture.length)
198
+ result_hash[k][res_key] = res_values
199
+ end
200
+ end
201
+ end
202
+ result_hash
203
+ end
204
+
205
+ def update
206
+ error "no app yet, create first" unless config[:app]
207
+
208
+ Dir.mktmpdir do |dir|
209
+ Dir.chdir(dir) do
210
+ system "git init -q"
211
+ system "git remote add origin git@github.com:wcdolphin/heroku-benchserver.git"
212
+ system "git remote add heroku git@#{heroku_git_domain}:#{config[:app]}.git"
213
+ pullres = capture { system "git pull --quiet origin master"}
214
+ pushres = capture { system "git push heroku master --quiet"}
215
+ end
216
+ end
217
+ end
218
+
219
+ def capture
220
+ results = $stdout = StringIO.new
221
+ yield
222
+ $stdout = STDOUT
223
+ results.close_write
224
+ results.rewind
225
+ return results.read
226
+ end
227
+
228
+
229
+
230
+ #Yeah, we are windows compatible. Because you should be too.
231
+ def null_dev
232
+ return test(?e, '/dev/null') ? '/dev/null' : 'NUL:'
233
+ end
234
+
235
+ def action(message)
236
+ print "#{message}... "
237
+ yield
238
+ puts "done"
239
+ end
240
+
241
+ def heroku(command)
242
+ %x{ env BUNDLE_GEMFILE= heroku #{command} 2>&1 }
243
+ end
244
+
245
+ def config_file
246
+ File.expand_path("~/.herokubench")
247
+ end
248
+
249
+ def config
250
+ read_config
251
+ end
252
+
253
+ def read_config
254
+ return {} unless File.exists?(config_file)
255
+ config = YAML.load_file(config_file)
256
+ config.is_a?(Hash) ? config : {}
257
+ end
258
+
259
+ def write_config(config)
260
+ full_config = read_config.merge(config)
261
+ File.open(config_file, "w") do |file|
262
+ file.puts YAML.dump(full_config)
263
+ end
264
+ end
265
+
266
+ def error(message)
267
+ puts "!! #{message}"
268
+ exit 1
269
+ end
270
+
271
+ def server_path
272
+ File.expand_path("../../../server", __FILE__)
273
+ end
274
+
275
+ #
276
+ # heroku_git_domain checks to see if the heroku-accounts plugin is present,
277
+ # and if so, it will set the domain to the one that matches the credentials
278
+ # for the currently set account
279
+ #
280
+ def heroku_git_domain
281
+ suffix = %x{ git config heroku.account }
282
+ suffix = "com" if suffix.nil? or suffix.strip == ""
283
+ "heroku.#{suffix.strip}"
284
+ end
285
+
286
+ end
@@ -0,0 +1,5 @@
1
+ module HerokuBench
2
+
3
+ VERSION = "0.0.7"
4
+
5
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: herokubench
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
+ platform: ruby
6
+ authors:
7
+ - Cory Dolphin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-06-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: heroku
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 2.26.0
20
+ - - <
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 2.26.0
30
+ - - <
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: thor
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ version: 0.18.1
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 0.18.1
47
+ - !ruby/object:Gem::Dependency
48
+ name: ruby-progressbar
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.1.1
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: 1.1.1
61
+ description: ! " Make it rain on the cloud.\n\n herokubench, or hb for short,
62
+ is a simple gem which eanbles you to easily load test websites,\n using a server
63
+ hosted by Heroku (on AWS). The gem manages deploying\n an app with no running
64
+ dynos (free), and abuses the concept of one-off\n jobs to run the Apache Benchmark,
65
+ ab.\n\n Confused? Checkout `hb help`\n"
66
+ email: wcdolphin@gmail.com
67
+ executables:
68
+ - hb
69
+ extensions: []
70
+ extra_rdoc_files: []
71
+ files:
72
+ - bin/hb
73
+ - lib/herokubench/bencher.rb
74
+ - lib/herokubench/cli.rb
75
+ - lib/herokubench/version.rb
76
+ - lib/herokubench.rb
77
+ - README.md
78
+ homepage: https://github.com/wcdolphin/heroku-bench
79
+ licenses: []
80
+ metadata: {}
81
+ post_install_message: Please run 'hbench create' to create your bench-server.
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.0.3
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: A gem to help load testing web applications deployed on AWS or Heroku, using
101
+ apache-bench
102
+ test_files: []