Description:
added load testing base library
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r313:0dcb692d14e0 - - 4 files changed: 606 inserted, 0 deleted

@@ -0,0 +1,2
1 + tmp
2 +
@@ -0,0 +1,148
1 + #
2 + # This file is part of a web load testing tool (currently having no name)
3 + # Copyright (C) 2008 Jittat Fakcharoenphol
4 + #
5 + # This program is free software; you can redistribute it and/or modify
6 + # it under the terms of the GNU General Public License as published by
7 + # the Free Software Foundation; either version 2 of the License, or
8 + # (at your option) any later version.
9 + #
10 + # This program is distributed in the hope that it will be useful, but
11 + # WITHOUT ANY WARRANTY; without even the implied warranty of
12 + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 + # General Public License for more details.
14 + #
15 + # You should have received a copy of the GNU General Public License
16 + # along with this program; if not, write to the Free Software
17 + # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 + # 02110-1301, USA.
19 + #
20 +
21 + require 'visitor_curl_cli'
22 +
23 + def show_usage
24 + puts <<USAGE
25 + using: ruby runner.rb <visitor_file> [<type> <number>] [<type> <number>] ... [options]
26 + * visitor_file : your visitor definition file, (with or without .rb)
27 + * type, number : the type and the number of visitors of that type
28 + * options : any of the following
29 + -t <sec> specify how long (in seconds)
30 + -d dry-run: run, but make no real http requests
31 + USAGE
32 + end
33 +
34 + def runner(visitor_lists, load_time=60, options={})
35 + visitors = []
36 + vcount = 0
37 +
38 + visitor_lists.each do |cname, num|
39 + begin
40 + c = Kernel.const_get(cname)
41 +
42 + num.times do
43 + visitors[vcount] = c.new(vcount+1)
44 + visitors[vcount].talkative = true
45 + vcount += 1
46 + end
47 + rescue NameError
48 + puts "Can't find class #{cname}"
49 + show_usage
50 + exit(0)
51 + end
52 + end
53 +
54 + puts "Having #{vcount} visitors"
55 +
56 + vthread = []
57 +
58 + all_start_time = Time.new
59 +
60 + # start all visitors
61 + vcount.times do |i|
62 + vthread[i] = Thread.new do
63 + visitors[i].run(:forever,options)
64 + end
65 + end
66 +
67 + # wait for load_time seconds
68 + sleep load_time
69 +
70 + visitors.each do |visitor| visitor.stop! end
71 +
72 + all_finish_time = Time.new
73 +
74 + begin
75 + # wait for all threads to stop
76 + vcount.times do |i|
77 + #puts "Waiting for thread #{i}, #{vthread[i].alive?}"
78 + vthread[i].join
79 + end
80 +
81 + rescue Interrupt
82 + # kill all remaining threads
83 + vcount.times do |i|
84 + vthread[i].kill if vthread[i].alive?
85 + end
86 + end
87 +
88 + # clean up
89 + visitors.each do |visitor|
90 + #puts "Clean up: #{visitor.id}"
91 + visitor.cleanup
92 + end
93 +
94 + #all_finish_time = Time.new
95 +
96 + total_req = 0
97 +
98 + vcount.times do |i|
99 + stat = visitors[i].statistics
100 + # puts "TYPE: #{visitors[i].class}"
101 + # puts "Total requested = #{stat[:num_requested]}"
102 + # puts "Average = #{stat[:avg_request_time]}"
103 + # puts "S.D. = #{stat[:std_dev]}"
104 + total_req += stat[:num_requested]
105 + end
106 +
107 + elapsed_time = all_finish_time - all_start_time
108 +
109 + puts
110 + puts "Total time = #{elapsed_time} sec."
111 + puts "Total requests = #{total_req}"
112 + puts "Trans. per sec = #{total_req/elapsed_time}"
113 + end
114 +
115 + ###########################
116 + # MAIN
117 + ###########################
118 +
119 + if ARGV.length==0
120 + show_usage
121 + exit(0)
122 + end
123 +
124 + visitor_file = ARGV.shift
125 + require visitor_file
126 +
127 + load_time = 60
128 + dry_run = false
129 +
130 + #build visitor list
131 + visitor_list = {}
132 + while ARGV.length>0
133 + key = ARGV.shift
134 +
135 + case key
136 + when '-d'
137 + dry_run = true
138 + when '-t'
139 + num = ARGV.shift.to_i
140 + load_time = num
141 + else
142 + num = ARGV.shift.to_i
143 + visitor_list[key] = num
144 + end
145 + end
146 +
147 + runner visitor_list, load_time, {:dry_run => dry_run}
148 +
@@ -0,0 +1,172
1 + require 'rubygems'
2 + require 'curb'
3 +
4 + class Visitor
5 +
6 + attr_accessor :talkative
7 +
8 + class << self
9 + attr_accessor :commands
10 + attr_accessor :base_url
11 + attr_accessor :cookies_stored
12 + end
13 +
14 + def initialize(id=0)
15 + # initialize nil class variable
16 + puts self.class.base_url
17 + self.class.base_url = "" if (self.class.base_url) == nil
18 + self.class.cookies_stored = false if self.class.cookies_stored == nil
19 +
20 + @id = id
21 + @curl = Curl::Easy.new
22 + @curl.enable_cookies = self.class.cookies_stored
23 + @curl.cookiejar = "mycookies"
24 + @statistics = Array.new
25 + @talkative = false
26 + end
27 +
28 + def self.site_url(url)
29 + self.base_url = url
30 + end
31 +
32 + def self.stores_cookies
33 + self.cookies_stored = true
34 + end
35 +
36 + def self.preprocess_param_hash(params)
37 + return {} if params==nil
38 + plist = {}
39 + params.each do |key,val|
40 + if key.is_a? Symbol
41 + key_s = key.to_s
42 + else
43 + key_s = key
44 + end
45 + plist[key_s] = val
46 + end
47 + plist
48 + end
49 +
50 + def self.get(url,params=nil)
51 + self.commands = [] if self.commands==nil
52 + self.commands << {
53 + :command => :get,
54 + :url => url,
55 + :params => Visitor.preprocess_param_hash(params) }
56 + end
57 +
58 + def self.post(url,params=nil,options=nil)
59 + self.commands = [] if self.commands==nil
60 + self.commands << { :command => :post,
61 + :url => url,
62 + :params => Visitor.preprocess_param_hash(params),
63 + :options => options }
64 + end
65 +
66 + def get(url,params)
67 + #build url
68 + full_url = "#{self.class.base_url}#{url}"
69 + if params!=nil and params.length!=0
70 + full_url += '?'
71 + params.each do |key,val|
72 + if full_url.slice(-1..-1)!='?'
73 + full_url += '&'
74 + end
75 + full_url += @curl.escape(key) + '=' + @curl.escape(val)
76 + end
77 + end
78 + @curl.url = full_url
79 + @curl.http_get
80 + end
81 +
82 + def post(url,params,options)
83 + @curl.url = "#{self.class.base_url}#{url}"
84 + if options!=nil and options[:multipart]==true
85 + @curl.multipart_form_post = true
86 + end
87 + #build post fields
88 + fields = []
89 + params.each do |key,val|
90 + if val.is_a? Hash
91 + case val[:type]
92 + when :file
93 + fields << Curl::PostField.file(key,val[:data])
94 + end
95 + else
96 + fields << Curl::PostField.content(key,val.to_s)
97 + end
98 + end
99 + @curl.http_post *fields
100 + end
101 +
102 + def run(option=nil)
103 +
104 + if (option==nil) or (option==:once)
105 + times = 1
106 + elsif (option==:forever)
107 + times = -1
108 + else
109 + times = option
110 + end
111 +
112 + while times!=0
113 + self.class.commands.each do |cmd|
114 + puts "#{@id}: #{cmd[:command]} #{cmd[:url]}" if @talkative
115 +
116 + start_time = Time.new
117 +
118 + case cmd[:command]
119 + when :get
120 + get cmd[:url], cmd[:params]
121 + when :post
122 + post cmd[:url], cmd[:params], cmd[:options]
123 + end
124 +
125 + finish_time = Time.new
126 +
127 + @statistics << {
128 + :url => "#{cmd[:command]}:#{cmd[:url]}",
129 + :time => finish_time - start_time }
130 + end
131 +
132 + if times!=-1 # infinity times
133 + times -= 1
134 + end
135 + end
136 + end
137 +
138 + def show_raw_stat
139 + @statistics.each do |stat|
140 + puts "#{stat[:url]} => #{stat[:time]}"
141 + end
142 + end
143 +
144 + def statistics
145 + num_requested = @statistics.length
146 + totaltime = 0.0
147 + @statistics.each { |stat| totaltime += stat[:time] }
148 +
149 + if num_requested>0
150 + average_request_time = totaltime / num_requested
151 + else
152 + average_request_time = 0
153 + end
154 +
155 + sq_sum = 0.0
156 + @statistics.each do |stat|
157 + sq_sum += (stat[:time]-average_request_time) ** 2
158 + end
159 + if num_requested>1
160 + sd = Math.sqrt(sq_sum/(num_requested-1))
161 + else
162 + sd = 0
163 + end
164 +
165 + return {
166 + :num_requested => num_requested,
167 + :avg_request_time => average_request_time,
168 + :std_dev => sd
169 + }
170 + end
171 + end
172 +
@@ -0,0 +1,284
1 + #
2 + # This file is part of a web load testing tool (currently having no name)
3 + # Copyright (C) 2008 Jittat Fakcharoenphol
4 + #
5 + # This program is free software; you can redistribute it and/or modify
6 + # it under the terms of the GNU General Public License as published by
7 + # the Free Software Foundation; either version 2 of the License, or
8 + # (at your option) any later version.
9 + #
10 + # This program is distributed in the hope that it will be useful, but
11 + # WITHOUT ANY WARRANTY; without even the implied warranty of
12 + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 + # General Public License for more details.
14 + #
15 + # You should have received a copy of the GNU General Public License
16 + # along with this program; if not, write to the Free Software
17 + # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 + # 02110-1301, USA.
19 + #
20 +
21 + require 'rubygems'
22 + require 'uri'
23 +
24 + class Visitor
25 +
26 + attr_accessor :talkative
27 +
28 + class << self
29 + attr_accessor :commands
30 + attr_accessor :base_url
31 + attr_accessor :cookies_stored
32 + end
33 +
34 + def get_cookie_fname
35 + "cookies.#{@id}"
36 + end
37 +
38 + def get_output_fname
39 + "output.#{@id}"
40 + end
41 +
42 + def id
43 + @id
44 + end
45 +
46 + def initialize(id=0)
47 + # initialize nil class variable
48 + self.class.base_url = "" if (self.class.base_url) == nil
49 + self.class.cookies_stored = false if self.class.cookies_stored == nil
50 +
51 + @id = id
52 + @cookies_fname = get_cookie_fname
53 + @output_fname = get_output_fname
54 + @statistics = Array.new
55 + @talkative = false
56 +
57 + @stopped = false
58 + end
59 +
60 + def cleanup
61 + trial = 0
62 + while FileTest.exists?(@cookies_fname)
63 + File.delete(@cookies_fname)
64 + if FileTest.exists?(@cookies_fname)
65 + # wait until system returns
66 + puts "STILL HERE"
67 + sleep 1
68 + trial += 1
69 + break if trial>10
70 + end
71 + end
72 +
73 + while FileTest.exists?(@output_fname)
74 + File.delete(@output_fname)
75 + if FileTest.exists?(@output_fname)
76 + # wait until system returns
77 + sleep 1
78 + trial += 1
79 + break if trial>10
80 + end
81 + end
82 + end
83 +
84 + def self.site_url(url)
85 + self.base_url = url
86 + end
87 +
88 + def self.stores_cookies
89 + self.cookies_stored = true
90 + end
91 +
92 + def self.preprocess_param_hash(params)
93 + return {} if params==nil
94 + plist = {}
95 + params.each do |key,val|
96 + if key.is_a? Symbol
97 + key_s = key.to_s
98 + else
99 + key_s = key
100 + end
101 + plist[key_s] = val
102 + end
103 + plist
104 + end
105 +
106 + def self.get(url,params=nil)
107 + self.commands = [] if self.commands==nil
108 + self.commands << {
109 + :command => :get,
110 + :url => url,
111 + :params => Visitor.preprocess_param_hash(params) }
112 + end
113 +
114 + def self.post(url,params=nil,options=nil)
115 + self.commands = [] if self.commands==nil
116 + self.commands << { :command => :post,
117 + :url => url,
118 + :params => Visitor.preprocess_param_hash(params),
119 + :options => options }
120 + end
121 +
122 + def substitute_id(st)
123 + return st if !(st.is_a? String)
124 + st.gsub(/(()|(\$))\$\{id\}/) do |s|
125 + if s=="${id}"
126 + @id.to_s
127 + else
128 + "${id}"
129 + end
130 + end
131 + end
132 +
133 + def encode_params(params)
134 + enc = ""
135 + if params!=nil and params.length!=0
136 + params.each do |key,val|
137 + if enc != ""
138 + enc += '&'
139 + end
140 + val = substitute_id(val)
141 + enc += URI.escape(key) + '=' + URI.escape(val.to_s)
142 + end
143 + end
144 + enc
145 + end
146 +
147 + def get(url,params)
148 + #build url
149 +
150 + #puts "----------------------cookies-----------"
151 + #system("cat #{@cookies_fname}")
152 + #puts "----------------------cookies-----------"
153 +
154 + full_url = "#{self.class.base_url}#{url}"
155 + if params!=nil and params.length!=0
156 + full_url += '?' + encode_params(params)
157 + end
158 +
159 + cmd = "curl -k -b #{@cookies_fname} -D #{@cookies_fname} #{full_url} " +
160 + " -s -L -o #{@output_fname}"
161 + #puts ">>>>>>>>>>>>>>>>>> " + cmd
162 + system(cmd)
163 + #system("cat #{@output_fname}")
164 + end
165 +
166 + def post(url,params,options)
167 + #puts "----------------------cookies-----------"
168 + #system("cat #{@cookies_fname}")
169 + #puts "----------------------cookies-----------"
170 +
171 + full_url = "#{self.class.base_url}#{url}"
172 + params_str = ""
173 + if options!=nil and options[:multipart]==true
174 + params.each do |key,val|
175 + if val.is_a? Hash
176 + case val[:type]
177 + when :file
178 + dval = substitute_id(val[:data])
179 + params_str += " -F \"#{key}=@#{dval.to_s}\""
180 + end
181 + else
182 + val = substitute_id(val)
183 + params_str += " -F \"#{key}=#{URI.escape(val.to_s)}\""
184 + end
185 + end
186 + else
187 + params_str += "-d \"#{encode_params(params)}\""
188 + end
189 +
190 + #puts params_str
191 +
192 + cmd = "curl -L -k -b #{@cookies_fname} -D #{@cookies_fname} " +
193 + " #{params_str} #{full_url} -s -o #{@output_fname}"
194 + #puts ">>>>>>>>>>>>>>>>>>>>>>>>>>> POST: " + cmd
195 + system(cmd)
196 + #system("cat #{@output_fname}")
197 + end
198 +
199 + def stop!
200 + @stopped = true
201 + end
202 +
203 + def run(times=nil, options={})
204 + times = 1 if times == :once
205 +
206 + @stopped = false
207 + while times!=0
208 + self.class.commands.each do |cmd|
209 + puts "#{@id}: #{cmd[:command]} #{cmd[:url]}" if @talkative
210 +
211 + start_time = Time.new
212 +
213 + if !options[:dry_run]
214 + case cmd[:command]
215 + when :get
216 + get cmd[:url], cmd[:params]
217 + when :post
218 + post cmd[:url], cmd[:params], cmd[:options]
219 + end
220 + end
221 +
222 + finish_time = Time.new
223 +
224 + break if @stopped
225 +
226 + @statistics << {
227 + :url => "#{cmd[:command]}:#{cmd[:url]}",
228 + :time => finish_time - start_time }
229 + end
230 +
231 + times -= 1 if times.is_a? Integer #otherwise, run forever
232 +
233 + break if @stopped
234 + end
235 + end
236 +
237 + def show_raw_stat
238 + @statistics.each do |stat|
239 + puts "#{stat[:url]} => #{stat[:time]}"
240 + end
241 + end
242 +
243 + def statistics
244 + num_requested = @statistics.length
245 + totaltime = 0.0
246 + @statistics.each { |stat| totaltime += stat[:time] }
247 +
248 + if num_requested>0
249 + average_request_time = totaltime / num_requested
250 + else
251 + average_request_time = 0
252 + end
253 +
254 + sq_sum = 0.0
255 + @statistics.each do |stat|
256 + sq_sum += (stat[:time]-average_request_time) ** 2
257 + end
258 + if num_requested>1
259 + sd = Math.sqrt(sq_sum/(num_requested-1))
260 + else
261 + sd = 0
262 + end
263 +
264 + return {
265 + :num_requested => num_requested,
266 + :avg_request_time => average_request_time,
267 + :std_dev => sd
268 + }
269 + end
270 + end
271 +
272 + def visitor(cname,&blk)
273 + c = Class.new(Visitor)
274 + begin
275 + Object.const_set(cname,c)
276 + rescue NameError
277 + puts <<ERROR
278 + Error on type #{cname}.
279 + Type name should be capitalized and follow Ruby constant naming rule.
280 + ERROR
281 + exit(0)
282 + end
283 + c.instance_eval(&blk)
284 + end
You need to be logged in to leave comments. Login now