front_end_loader 0.2.3

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/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2011-2012 Brewster Inc., Aubrey Holland
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,121 @@
1
+ # Front End Loader
2
+
3
+ Front End Loader is a Ruby DSL for declaring load tests. It works in the spirit of
4
+ tools like JMeter, by simulating a number of users performing a scripted set of actions
5
+ and displaying metrics about response times and error rates as the requests are performed.
6
+ Unlike GUI tools like JMeter, however, front_end_loader makes it very simple to declare
7
+ your requests and to pass data between requests, by looking at the responses to gather data.
8
+
9
+ ## Install
10
+ gem install front_end_loader
11
+
12
+ ## Creating an Experiment
13
+
14
+ In order to create a test, just declare a FrontEndLoader::Experiment object:
15
+
16
+ ```ruby
17
+ require 'front_end_loader'
18
+
19
+ experiment = FrontEndLoader::Experiment.new.tap do |e|
20
+ e.user_count = 20
21
+ e.loop_count = 5
22
+ e.domain = 'https://www.google.com'
23
+ e.basic_auth('unreal_login', 'unreal_password')
24
+ e.default_parameters = { 'unnecessary' => 'true' }
25
+ e.debug = '/tmp/front_end_loader.txt'
26
+
27
+ e.requests do |r|
28
+ ...
29
+ end
30
+ end
31
+
32
+ experiment.run
33
+ ```
34
+
35
+ This block declares an experiment that:
36
+
37
+ * simulates 20 users simultaneously interacting with the system
38
+ * executes the request script five times per user before exiting. You can specify infinite loops by either not calling loop_count or passing -1
39
+ * will operate against the google.com domain
40
+ * uses http basic auth
41
+ * passes a default parameter of unnecessary to each request, and
42
+ * writes debugging output to /tmp/front_end_loader.txt
43
+
44
+ It then runs the experiment, which causes the requests to start flowing and output to be displayed
45
+ on the screen. The requests method on the experiment is where you will define the script to be run
46
+ loop_count times for each of the simulated users:
47
+
48
+ ```ruby
49
+ e.requests do |r|
50
+
51
+ word = nil
52
+
53
+ r.get('test_search', '/search', :q => 'test') do |response|
54
+ word = response.body.
55
+ split(/\s/).
56
+ reject { |i| i.length < 3 || i.length > 10 }.
57
+ sort_by { rand }.
58
+ first
59
+ end
60
+
61
+ e.write_debug(word)
62
+
63
+ r.get('random_word_search', '/search', :q => word)
64
+
65
+ r.get('privacy_policy', '/intl/en/policies')
66
+
67
+ # r.post(...)
68
+ # r.put(...)
69
+ # r.delete(...)
70
+ end
71
+ ```
72
+
73
+ For each request, arguments are:
74
+
75
+ * the label to use when tracking it in the display
76
+ * the path
77
+ * parameters, as a hash
78
+ * for post and put requests, a data object to use as the request body
79
+
80
+ All request declarations can take a block that will be passed the response from that request. The response
81
+ is a Patron::Response object and can be used to access data and pass it into further requests. Each iteration
82
+ of the script will be run in order and will not affect other iterations that may be running.
83
+
84
+ ## Running the experiment
85
+
86
+ Excuting an experiment will produce output like this:
87
+
88
+ ```
89
+ ------------------------------------------------------------------------------------------------------
90
+ | call | count | avg time | max time | errors | error % | throughput |
91
+ ------------------------------------------------------------------------------------------------------
92
+ | profile | 40 | 0.252 | 0.731 | 0 | 0.0 | 140 |
93
+ | random search | 40 | 0.275 | 0.491 | 0 | 0.0 | 140 |
94
+ | filtered_search | 40 | 0.28 | 0.67 | 0 | 0.0 | 140 |
95
+ | suggestions | 40 | 0.264 | 0.624 | 0 | 0.0 | 140 |
96
+ | autocomplete | 38 | 0.234 | 0.456 | 0 | 0.0 | 133 |
97
+ | filtered autocomplete | 37 | 0.204 | 0.323 | 0 | 0.0 | 130 |
98
+ | services | 37 | 0.203 | 0.476 | 0 | 0.0 | 130 |
99
+ | service types | 37 | 0.185 | 0.456 | 0 | 0.0 | 130 |
100
+ | me | 36 | 0.25 | 0.555 | 0 | 0.0 | 126 |
101
+ | | | | | | | |
102
+ | TOTAL | 345 | 0.238 | 0.731 | 0 | 0.0 | 1209 |
103
+ ------------------------------------------------------------------------------------------------------
104
+ run time: 0:00:17
105
+ ```
106
+
107
+ Throughput is measured in requests per minute and note that because each "user" is running though the script
108
+ in series, the throughput for an individual request is not as high as you would expect by running only that request
109
+ over and over again.
110
+
111
+ This display accepts the following keyboard controls:
112
+
113
+ * c - reset the data
114
+ * d - write the contents of the screen to the debug file
115
+ * p - pause the scripts, so the data will remain static and no requests will be made
116
+ * q - quit
117
+ * s - start the scripts again when paused
118
+
119
+ ## <a name="copyright"></a>Copyright
120
+ Copyright (c) 2011-2012 Brewster Inc., Aubrey Holland
121
+ See [LICENSE](https://github.com/brewster/front_end_loader/blob/master/LICENSE) for details.
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ task :default => :test
4
+
5
+ ## helper functions
6
+
7
+ def name
8
+ @name ||= Dir['*.gemspec'].first.split('.').first
9
+ end
10
+
11
+ def version
12
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
13
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
14
+ end
15
+
16
+ def gemspec_file
17
+ "#{name}.gemspec"
18
+ end
19
+
20
+ def replace_header(head, header_name)
21
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
22
+ end
23
+
24
+ ## standard tasks
25
+
26
+ RSpec::Core::RakeTask.new(:test)
27
+
28
+ desc "Generate #{gemspec_file}"
29
+ task :gemspec do
30
+ # read spec file and split out manifest section
31
+ spec = File.read(gemspec_file)
32
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
33
+
34
+ # replace name version and date
35
+ replace_header(head, :name)
36
+ replace_header(head, :version)
37
+
38
+ # determine file list from git ls-files
39
+ files = `git ls-files`.
40
+ split("\n").
41
+ sort.
42
+ reject { |file| file =~ /^\./ }.
43
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
44
+ map { |file| " #{file}" }.
45
+ join("\n")
46
+
47
+ # piece file back together and write
48
+ manifest = " s.files = %w[\n#{files}\n ]\n"
49
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
50
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
51
+ puts "Updated #{gemspec_file}"
52
+ end
@@ -0,0 +1,36 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.3.5") if s.respond_to? :required_rubygems_version=
4
+
5
+ s.name = 'front_end_loader'
6
+ s.version = '0.2.3'
7
+
8
+ s.summary = 'A framework for doing declarative load testing in ruby'
9
+ s.description = <<-EOF
10
+ Front End Loader allows clients to declare load tests using a pure-Ruby DSL.
11
+ This means that it is very simple to pass data between requests or to interact
12
+ with your systems in dynamic, complex ways.
13
+ EOF
14
+ s.authors = ['Aubrey Holland']
15
+ s.email = 'aubreyholland@gmail.com'
16
+ s.homepage = 'https://github.com/brewster/front_end_loader'
17
+
18
+ s.add_dependency 'patron'
19
+
20
+ # = MANIFEST =
21
+ s.files = %w[
22
+ Gemfile
23
+ LICENSE
24
+ README.md
25
+ Rakefile
26
+ front_end_loader.gemspec
27
+ lib/front_end_loader.rb
28
+ lib/front_end_loader/experiment.rb
29
+ lib/front_end_loader/request.rb
30
+ lib/front_end_loader/request_manager.rb
31
+ lib/front_end_loader/screen.rb
32
+ ]
33
+ # = MANIFEST =
34
+
35
+ s.test_files = s.files.select { |path| path =~ %r{^spec/*/.+\.rb} }
36
+ end
@@ -0,0 +1,277 @@
1
+ module FrontEndLoader
2
+ class Experiment
3
+ attr_accessor :domain
4
+ attr_accessor :user_count
5
+ attr_accessor :loop_count
6
+ attr_accessor :default_parameters
7
+ attr_reader :basic_auth_enabled
8
+ attr_reader :basic_auth_user
9
+ attr_reader :basic_auth_password
10
+ attr_reader :screen
11
+
12
+ attr_reader :run_start_time
13
+ attr_reader :run_completed_time
14
+ attr_reader :running
15
+ attr_reader :call_times
16
+ attr_reader :call_error_counts
17
+ attr_reader :call_max_times
18
+
19
+ def initialize
20
+ @screen = Screen.new(self)
21
+ @running = false
22
+ @mutex = Mutex.new
23
+ @debug_mutex = Mutex.new
24
+ @loop_count = -1
25
+ @paused = false
26
+ @run_completed_time = nil
27
+ clear_data
28
+ end
29
+
30
+ def synchronize(&block)
31
+ @mutex.synchronize do
32
+ block.call
33
+ end
34
+ end
35
+
36
+ def debug=(file)
37
+ @debug_file = File.open(file, 'a')
38
+ end
39
+
40
+ def write_debug(data)
41
+ if @debug_file
42
+ @debug_mutex.synchronize do
43
+ @debug_file.puts(data)
44
+ @debug_file.flush
45
+ end
46
+ end
47
+ end
48
+
49
+ def write_screen_to_debug
50
+ if @debug_file
51
+ @debug_mutex.synchronize do
52
+ @screen.write_debug(@debug_file)
53
+ end
54
+ end
55
+ end
56
+
57
+ def clear_data
58
+ @mutex.synchronize do
59
+ @call_counts ||= Hash.new { |h,k| h[k] = 0 }
60
+ @call_counts.keys.each { |k| @call_counts[k] = 0 }
61
+
62
+ @call_times ||= Hash.new { |h,k| h[k] = 0.0 }
63
+ @call_times.keys.each { |k| @call_times[k] = 0.0 }
64
+
65
+ @call_max_times ||= Hash.new { |h,k| h[k] = 0.0 }
66
+ @call_max_times.keys.each { |k| @call_max_times[k] = 0.0 }
67
+
68
+ @call_error_counts ||= Hash.new { |h,k| h[k] = 0 }
69
+ @call_error_counts .keys.each { |k| @call_error_counts[k] = 0 }
70
+
71
+ @call_times_last_25 ||= Hash.new { |h,k| h[k] = [] }
72
+ @call_times_last_25.keys.each { |k| @call_times_last_25[k] = [] }
73
+
74
+ @error_counts_by_type ||= Hash.new { |h,k| h[k] = 0 }
75
+ @error_counts_by_type.keys.each { |k| @error_counts_by_type[k] = 0 }
76
+
77
+ if @run_start_time
78
+ @run_start_time = Time.now
79
+ else
80
+ @run_start_time = nil
81
+ end
82
+ end
83
+ end
84
+
85
+ def basic_auth(user, password)
86
+ @basic_auth_enabled = true
87
+ @basic_auth_user = user
88
+ @basic_auth_password = password
89
+ end
90
+
91
+ def requests(&block)
92
+ @request_block = block
93
+ end
94
+
95
+ def run_completed!
96
+ @run_completed_time = Time.now
97
+ end
98
+
99
+ def run
100
+ @running = true
101
+ @run_start_time = Time.now
102
+
103
+ threads = (1..user_count).to_a.map do
104
+ Thread.new(self, @request_block) do |experiment, request_block|
105
+ loops_left = experiment.loop_count
106
+ while(loops_left != 0)
107
+ if experiment.paused?
108
+ sleep(0.25)
109
+ elsif experiment.quitting?
110
+ loops_left = 0
111
+ else
112
+ request_manager = RequestManager.new(experiment, experiment.http_session)
113
+ request_block.call(request_manager)
114
+ loops_left -= 1
115
+ end
116
+ end
117
+ experiment.run_completed!
118
+ end
119
+ end
120
+
121
+ threads << Thread.new(self) do |experiment|
122
+ while (!experiment.quitting?)
123
+ if experiment.paused?
124
+ sleep(0.25)
125
+ else
126
+ experiment.screen.refresh
127
+ sleep(0.1)
128
+ end
129
+ end
130
+ end
131
+
132
+ threads << Thread.new(self) do |experiment|
133
+ while (!experiment.quitting?)
134
+ ch = Curses.getch
135
+ if ch == 'c'
136
+ experiment.clear_data
137
+ elsif ch == 'd'
138
+ experiment.write_screen_to_debug
139
+ elsif ch == 'p'
140
+ experiment.pause
141
+ elsif ch == 'q'
142
+ experiment.quit
143
+ elsif ch == 's'
144
+ experiment.clear_data
145
+ experiment.go
146
+ end
147
+ end
148
+ end
149
+
150
+ begin
151
+ threads.each(&:run)
152
+ threads.each(&:join)
153
+ rescue Interrupt
154
+ @screen.close
155
+ end
156
+
157
+ @screen.close
158
+ end
159
+
160
+ def http_session
161
+ Patron::Session.new.tap do |session|
162
+ session.base_url = domain
163
+ session.insecure = true
164
+ session.max_redirects = 0
165
+ if basic_auth_enabled
166
+ session.auth_type = :basic
167
+ session.username = basic_auth_user
168
+ session.password = basic_auth_password
169
+ session.connect_timeout = 10
170
+ session.timeout = 500
171
+ end
172
+ end
173
+ end
174
+
175
+ def paused?
176
+ @paused
177
+ end
178
+
179
+ def pause
180
+ @paused = true
181
+ end
182
+
183
+ def go
184
+ @paused = false
185
+ end
186
+
187
+ def quitting?
188
+ @quitting
189
+ end
190
+
191
+ def quit
192
+ @quitting = true
193
+ end
194
+
195
+ def time_call(name, &block)
196
+ begin
197
+ start = Time.now
198
+ response = block.call
199
+ time = Time.now - start
200
+ @mutex.synchronize do
201
+ @call_times[name] += time
202
+ @call_max_times[name] = time if time > @call_max_times[name]
203
+ unless response.status >= 200 && response.status < 400
204
+ write_debug(response.body)
205
+ @call_error_counts[name] += 1
206
+ @error_counts_by_type[response.status] += 1
207
+ end
208
+ @call_counts[name] += 1
209
+ @call_times_last_25[name].unshift(time)
210
+ @call_times_last_25[name] = @call_times_last_25[name].slice(0, 25)
211
+ end
212
+ response
213
+ rescue Patron::TimeoutError
214
+ add_timeout(name)
215
+ end
216
+ end
217
+
218
+ def add_timeout(name)
219
+ @mutex.synchronize do
220
+ @call_counts[name] += 1
221
+ @call_times[name] += 0
222
+ @call_error_counts[name] += 1
223
+ @error_counts_by_type['Timeout'] += 1
224
+ end
225
+ end
226
+
227
+ def call_counts
228
+ @call_counts.dup
229
+ end
230
+
231
+ def total_times
232
+ @call_times.dup
233
+ end
234
+
235
+ def average_times
236
+ @call_times.keys.inject({}) do |hash, name|
237
+ if @call_counts[name] == 0
238
+ hash[name] = 0.0
239
+ else
240
+ hash[name] = @call_times[name] / @call_counts[name].to_f
241
+ end
242
+ hash
243
+ end
244
+ end
245
+
246
+ def max_times
247
+ @call_max_times.dup
248
+ end
249
+
250
+ def error_counts
251
+ @call_error_counts.dup
252
+ end
253
+
254
+ def error_types
255
+ @error_counts_by_type.dup
256
+ end
257
+
258
+ def error_percents
259
+ @call_counts.keys.inject({}) do |hash, name|
260
+ if @call_counts[name] && @call_counts[name] > 0 && @call_error_counts[name] && @call_error_counts[name] > 0
261
+ hash[name] = (@call_error_counts[name].to_f / @call_counts[name].to_f) * 100.0
262
+ else
263
+ hash[name] = 0.0
264
+ end
265
+ hash
266
+ end
267
+ end
268
+
269
+ def throughput
270
+ delta = ((@run_completed_time || Time.now) - @run_start_time) / 60.0
271
+ @call_counts.keys.inject({}) do |hash, name|
272
+ hash[name] = @call_counts[name].to_f / delta
273
+ hash
274
+ end
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,30 @@
1
+ module FrontEndLoader
2
+ class Request
3
+ def initialize(experiment, session, method, name, path, params, data, response_block)
4
+ @experiment = experiment
5
+ @session = session
6
+ @method = method
7
+ @name = name
8
+ @path = path
9
+ @params = URI.encode(@experiment.default_parameters.merge(params).map { |k,v| "#{k}=#{v}" }.join('&'))
10
+ @data = data
11
+ @response_block = response_block
12
+ end
13
+
14
+ def run
15
+ response = nil
16
+ if [:get, :delete].include?(@method)
17
+ response = @experiment.time_call(@name) do
18
+ @session.__send__(@method, "#{@path}?#{@params}")
19
+ end
20
+ else
21
+ response = @experiment.time_call(@name) do
22
+ @session.__send__(@method, "#{@path}?#{@params}", @data, {'Content-Type' => 'application/json'})
23
+ end
24
+ end
25
+ if @response_block && response.is_a?(Patron::Response)
26
+ @response_block.call(response)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ module FrontEndLoader
2
+ class RequestManager
3
+ def initialize(experiment, session)
4
+ @experiment = experiment
5
+ @session = session
6
+ end
7
+
8
+ def get(name, path, params={}, &block)
9
+ Request.new(@experiment, @session, :get, name, path, params, nil, block).run
10
+ end
11
+
12
+ def post(name, path, params={}, data="{}", &block)
13
+ Request.new(@experiment, @session, :post, name, path, params, data, block).run
14
+ end
15
+
16
+ def put(name, path, params={}, data="{}", &block)
17
+ Request.new(@experiment, @session, :put, name, path, params, data, block).run
18
+ end
19
+
20
+ def delete(name, path, params={}, &block)
21
+ Request.new(@experiment, @session, :delete, name, path, params, nil, block).run
22
+ end
23
+
24
+ def debug(data)
25
+ @experiment.write_debug(data)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,226 @@
1
+ require 'curses'
2
+
3
+ module FrontEndLoader
4
+ class Screen
5
+
6
+ POSITIONS = {
7
+ :count => 0,
8
+ :average_time => 11,
9
+ :max_time => 22,
10
+ :errors => 33,
11
+ :error_percent => 44,
12
+ :throughput => 55
13
+ }
14
+
15
+ LENGTHS = {
16
+ :count => 9,
17
+ :average_time => 9,
18
+ :max_time => 9,
19
+ :errors => 9,
20
+ :error_percent => 9,
21
+ :throughput => 12
22
+ }
23
+
24
+ TOTAL_LENGTH = 66
25
+
26
+ def initialize(experiment)
27
+ @experiment = experiment
28
+ @entry_count = nil
29
+ @longest_name = 0
30
+ Curses.init_screen
31
+ Curses.nonl
32
+ Curses.cbreak
33
+ Curses.noecho
34
+ end
35
+
36
+ def refresh
37
+ run_start_time = nil
38
+ names = nil
39
+ counts_by_name = {}
40
+ times_by_name = {}
41
+ average_times_by_name = {}
42
+ max_times_by_name = {}
43
+ error_counts_by_name = {}
44
+ error_counts_by_type = {}
45
+ error_percents_by_name = {}
46
+ throughput_by_name = {}
47
+ total_calls = nil
48
+ total_time = nil
49
+ total_errors = nil
50
+ delta = nil
51
+ max_max_time = nil
52
+
53
+ @experiment.synchronize do
54
+ return if !@experiment.running || @experiment.call_counts.empty?
55
+
56
+ run_start_time = @experiment.run_start_time.dup
57
+ names = @experiment.call_counts.keys.dup
58
+
59
+ counts_by_name = @experiment.call_counts
60
+ times_by_name = @experiment.total_times
61
+ average_times_by_name = @experiment.average_times
62
+ max_times_by_name = @experiment.max_times
63
+ error_counts_by_name = @experiment.error_counts
64
+ error_counts_by_type = @experiment.error_types
65
+ error_percents_by_name = @experiment.error_percents
66
+ throughput_by_name = @experiment.throughput
67
+
68
+ total_calls = @experiment.call_counts.values.inject(0) { |s,i| s + i }
69
+ total_time = @experiment.call_times.values.inject(0) { |s,i| s + i }
70
+ total_errors = @experiment.call_error_counts.values.inject(0) { |s,i| s + i }
71
+ max_max_time = @experiment.call_max_times.values.max.round(3).to_s
72
+ delta = ((@experiment.run_completed_time || Time.now) - @experiment.run_start_time) / 60.0
73
+ end
74
+
75
+ if names.count != @entry_count
76
+ @entry_count = names.length
77
+ @longest_name = names.map(&:length).max
78
+ draw_outlines(names)
79
+ end
80
+
81
+ first_position = @longest_name + 6
82
+ line = 3
83
+ names.each do |name|
84
+ clear_line(line, first_position)
85
+ Curses.setpos(line, first_position + POSITIONS[:count])
86
+ Curses.addstr(counts_by_name[name].to_s)
87
+ Curses.setpos(line, first_position + POSITIONS[:average_time])
88
+ Curses.addstr(average_times_by_name[name].round(3).to_s)
89
+ Curses.setpos(line, first_position + POSITIONS[:max_time])
90
+ Curses.addstr(max_times_by_name[name].round(3).to_s)
91
+ Curses.setpos(line, first_position + POSITIONS[:errors])
92
+ Curses.addstr(error_counts_by_name[name].to_s)
93
+ Curses.setpos(line, first_position + POSITIONS[:error_percent])
94
+ Curses.addstr(error_percents_by_name[name].round(3).to_s)
95
+ Curses.setpos(line, first_position + POSITIONS[:throughput])
96
+ Curses.addstr(throughput_by_name[name].to_i.to_s)
97
+ line += 1
98
+ end
99
+ line += 1
100
+ clear_line(line, first_position)
101
+ Curses.setpos(line, first_position + POSITIONS[:count])
102
+ Curses.addstr(total_calls.to_s)
103
+ Curses.setpos(line, first_position + POSITIONS[:average_time])
104
+ Curses.addstr((total_time / total_calls.to_f).round(3).to_s)
105
+ Curses.setpos(line, first_position + POSITIONS[:max_time])
106
+ Curses.addstr(max_max_time.to_s)
107
+ Curses.setpos(line, first_position + POSITIONS[:errors])
108
+ Curses.addstr(total_errors.to_s)
109
+ Curses.setpos(line, first_position + POSITIONS[:error_percent])
110
+ Curses.addstr(((total_errors.to_f / total_calls.to_f) * 100.0).round(1).to_s)
111
+ Curses.setpos(line, first_position + POSITIONS[:throughput])
112
+ Curses.addstr((total_calls.to_f / delta).to_i.to_s)
113
+
114
+ line += 3
115
+ time = Time.now - run_start_time
116
+ hours = (time / 3600).to_i
117
+ minutes = (time / 60 - (hours * 60)).to_i
118
+ seconds = (time - (minutes * 60 + hours * 3600)).to_i
119
+ Curses.setpos(line, 3)
120
+ Curses.addstr("run time: #{hours}:#{'%02d' % minutes}:#{'%02d' % seconds}")
121
+
122
+ line += 2
123
+ error_counts_by_type.each do |type, count|
124
+ erase_line(line)
125
+ Curses.setpos(line, 3)
126
+ Curses.addstr("#{type}: #{count}")
127
+ line += 1
128
+ end
129
+
130
+ Curses.curs_set(0)
131
+ Curses.refresh
132
+ rescue StandardError => e
133
+ puts e.message
134
+ puts e.backtrace.first
135
+ end
136
+
137
+ def clear_line(line, first_position)
138
+ Curses.setpos(line, first_position + POSITIONS[:count])
139
+ Curses.addstr(' ' * LENGTHS[:count])
140
+ Curses.setpos(line, first_position + POSITIONS[:average_time])
141
+ Curses.addstr(' ' * LENGTHS[:average_time])
142
+ Curses.setpos(line, first_position + POSITIONS[:max_time])
143
+ Curses.addstr(' ' * LENGTHS[:max_time])
144
+ Curses.setpos(line, first_position + POSITIONS[:errors])
145
+ Curses.addstr(' ' * LENGTHS[:error_percent])
146
+ Curses.setpos(line, first_position + POSITIONS[:error_percent])
147
+ Curses.addstr(' ' * LENGTHS[:error_percent])
148
+ Curses.setpos(line, first_position + POSITIONS[:throughput])
149
+ Curses.addstr(' ' * LENGTHS[:throughput])
150
+ end
151
+
152
+ def erase_line(line)
153
+ Curses.setpos(line, 0)
154
+ Curses.addstr(' ' * TOTAL_LENGTH)
155
+ end
156
+
157
+ def draw_outlines(names)
158
+ first_position = @longest_name + 6
159
+ Curses.clear
160
+ Curses.setpos(0, 2)
161
+ Curses.addstr('-' * (first_position + TOTAL_LENGTH))
162
+ Curses.setpos(1, 1)
163
+ Curses.addstr(divider_string)
164
+ Curses.setpos(1, 3)
165
+ Curses.addstr('call')
166
+ Curses.setpos(1, first_position + POSITIONS[:count])
167
+ Curses.addstr('count')
168
+ Curses.setpos(1, first_position + POSITIONS[:average_time])
169
+ Curses.addstr('avg time')
170
+ Curses.setpos(1, first_position + POSITIONS[:max_time])
171
+ Curses.addstr('max time')
172
+ Curses.setpos(1, first_position + POSITIONS[:errors])
173
+ Curses.addstr('errors')
174
+ Curses.setpos(1, first_position + POSITIONS[:error_percent])
175
+ Curses.addstr('error %')
176
+ Curses.setpos(1, first_position + POSITIONS[:throughput])
177
+ Curses.addstr('throughput')
178
+ Curses.setpos(2, 2)
179
+ Curses.addstr('-' * (first_position + TOTAL_LENGTH))
180
+ line = 3
181
+ names.each do |name|
182
+ Curses.setpos(line, 1)
183
+ Curses.addstr(divider_string)
184
+ Curses.setpos(line, 3)
185
+ Curses.addstr(name)
186
+ line += 1
187
+ end
188
+ Curses.setpos(line, 1)
189
+ Curses.addstr(divider_string)
190
+ line += 1
191
+ Curses.setpos(line, 1)
192
+ Curses.addstr(divider_string)
193
+
194
+ Curses.setpos(line, 3)
195
+ Curses.addstr('TOTAL')
196
+ line += 1
197
+ Curses.setpos(line, 2)
198
+ Curses.addstr('-' * (first_position + TOTAL_LENGTH))
199
+ end
200
+
201
+ def divider_string
202
+ first_position = '|' + (' ' * (@longest_name + 2))
203
+ first_position + '| | | | | | |'
204
+ end
205
+
206
+ def close
207
+ Curses.curs_set(1)
208
+ Curses::close_screen
209
+ end
210
+
211
+ def write_debug(file)
212
+ (0..Curses.lines).each do |line|
213
+ line_string = ''
214
+ (0..Curses.cols).each do |col|
215
+ Curses.setpos(line, col)
216
+ line_string << Curses.inch.chr
217
+ end
218
+ line_string.strip!
219
+ unless line_string.empty?
220
+ file.puts(line_string)
221
+ end
222
+ end
223
+ file.flush
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,10 @@
1
+ require 'patron'
2
+ require 'uri'
3
+
4
+ %w(experiment request request_manager screen).each do |file|
5
+ require_relative "front_end_loader/#{file}"
6
+ end
7
+
8
+ module FrontEndLoader
9
+ VERSION = '0.2.2'
10
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: front_end_loader
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Aubrey Holland
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: patron
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '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: '0'
30
+ description: ! 'Front End Loader allows clients to declare load tests using a pure-Ruby
31
+ DSL.
32
+
33
+ This means that it is very simple to pass data between requests or to interact
34
+
35
+ with your systems in dynamic, complex ways.
36
+
37
+ '
38
+ email: aubreyholland@gmail.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - Gemfile
44
+ - LICENSE
45
+ - README.md
46
+ - Rakefile
47
+ - front_end_loader.gemspec
48
+ - lib/front_end_loader.rb
49
+ - lib/front_end_loader/experiment.rb
50
+ - lib/front_end_loader/request.rb
51
+ - lib/front_end_loader/request_manager.rb
52
+ - lib/front_end_loader/screen.rb
53
+ homepage: https://github.com/brewster/front_end_loader
54
+ licenses: []
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: 1.3.5
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 1.8.24
74
+ signing_key:
75
+ specification_version: 2
76
+ summary: A framework for doing declarative load testing in ruby
77
+ test_files: []