crusher 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ task :gem do
2
+ `gem build crusher.gemspec`
3
+ `mv crusher-*.gem tmp/`
4
+ end
data/bin/crush ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.join( File.dirname(__FILE__) + "/../lib" )
3
+ require 'crusher'
4
+ require 'eventmachine'
5
+ require 'active_support/core_ext'
6
+
7
+ config = Crusher::Configurator::Configuration.new('Crushfile')
8
+
9
+ target_name = ARGV[0].to_sym
10
+ scenario_name = ARGV[1].to_sym
11
+
12
+ target = config.targets[target_name]
13
+ scenario = config.scenarios[scenario_name]
14
+
15
+ puts "Fatal Error: Unknown target '#{target_name}'" unless target
16
+ puts "Fatal Error: Unknown scenario '#{scenario_name}'" unless scenario
17
+ exit(1) unless target && scenario
18
+
19
+ options = {}
20
+ options[:log_file] = ARGV[2]
21
+
22
+ Crusher::CrushSession.new(target, scenario, options)
@@ -0,0 +1,65 @@
1
+ module Crusher
2
+ module Configurator
3
+
4
+ class Configuration
5
+
6
+ attr_reader :targets, :scenarios
7
+
8
+ def initialize(config_path)
9
+ @targets = {}
10
+ @scenarios = {}
11
+ eval File.open(config_path).read
12
+ end
13
+
14
+ def target target_name, &block
15
+ @targets[target_name] = Crusher::Configurator::Target.new(&block)
16
+ end
17
+
18
+ def scenario scenario_name, &block
19
+ @scenarios[scenario_name] = Crusher::Configurator::Scenario.new(&block)
20
+ end
21
+
22
+ end
23
+
24
+ class Target
25
+
26
+ def initialize(*args, &block)
27
+ block.call self, *args
28
+ end
29
+
30
+ def uri(new_uri = nil)
31
+ return @uri unless new_uri
32
+ @uri = new_uri
33
+ end
34
+
35
+ end
36
+
37
+ class Scenario
38
+
39
+ attr_reader :launch_jobs, :phases
40
+
41
+ def initialize(*args, &block)
42
+ @launch_jobs = []
43
+ @phases = []
44
+ @duration = nil
45
+ block.call self, *args
46
+ end
47
+
48
+ def launch(count, type, &block)
49
+ @launch_jobs.push :type => type, :count => count, :proc => block
50
+ end
51
+
52
+ def phase(name, options = {}, &block)
53
+ @phases << options.merge( :name => name.to_s, :proc => block )
54
+ end
55
+
56
+ def duration(new_duration = nil)
57
+ return @duration unless new_duration
58
+ @duration = new_duration
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,562 @@
1
+ # cookie.rb is redistributed file which is originally included in Webagent
2
+ # version 0.6.2 by TAKAHASHI `Maki' Masayoshi. And it contains some bug fixes.
3
+ # You can download the entire package of Webagent from
4
+ # http://www.rubycolor.org/arc/.
5
+
6
+
7
+ # Cookie class
8
+ #
9
+ # I refered to w3m's source to make these classes. Some comments
10
+ # are quoted from it. I'm thanksful for author(s) of it.
11
+ #
12
+ # w3m homepage: http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/eng/
13
+
14
+ require 'uri'
15
+ require 'time'
16
+ require 'monitor'
17
+
18
+ class WebAgent
19
+
20
+ module CookieUtils
21
+
22
+ def head_match?(str1, str2)
23
+ str1 == str2[0, str1.length]
24
+ end
25
+
26
+ def tail_match?(str1, str2)
27
+ if str1.length > 0
28
+ str1 == str2[-str1.length..-1].to_s
29
+ else
30
+ true
31
+ end
32
+ end
33
+
34
+ def domain_match(host, domain)
35
+ domainname = domain.sub(/\.\z/, '').downcase
36
+ hostname = host.sub(/\.\z/, '').downcase
37
+ case domain
38
+ when /\d+\.\d+\.\d+\.\d+/
39
+ return (hostname == domainname)
40
+ when '.'
41
+ return true
42
+ when /^\./
43
+ # allows; host == rubyforge.org, domain == .rubyforge.org
44
+ return tail_match?(domainname, '.' + hostname)
45
+ else
46
+ return (hostname == domainname)
47
+ end
48
+ end
49
+
50
+ def total_dot_num(string)
51
+ string.scan(/\./).length()
52
+ end
53
+
54
+ end
55
+
56
+ class Cookie
57
+ include CookieUtils
58
+
59
+ attr_accessor :name, :value
60
+ attr_accessor :domain, :path
61
+ attr_accessor :expires ## for Netscape Cookie
62
+ attr_accessor :url
63
+ attr_writer :use, :secure, :discard, :domain_orig, :path_orig, :override
64
+
65
+ USE = 1
66
+ SECURE = 2
67
+ DOMAIN = 4
68
+ PATH = 8
69
+ DISCARD = 16
70
+ OVERRIDE = 32
71
+ OVERRIDE_OK = 32
72
+
73
+ def initialize()
74
+ @name = @value = @domain = @path = nil
75
+ @expires = nil
76
+ @url = nil
77
+ @use = @secure = @discard = @domain_orig = @path_orig = @override = nil
78
+ end
79
+
80
+ def discard?
81
+ @discard
82
+ end
83
+
84
+ def use?
85
+ @use
86
+ end
87
+
88
+ def secure?
89
+ @secure
90
+ end
91
+
92
+ def domain_orig?
93
+ @domain_orig
94
+ end
95
+
96
+ def path_orig?
97
+ @path_orig
98
+ end
99
+
100
+ def override?
101
+ @override
102
+ end
103
+
104
+ def flag
105
+ flg = 0
106
+ flg += USE if @use
107
+ flg += SECURE if @secure
108
+ flg += DOMAIN if @domain_orig
109
+ flg += PATH if @path_orig
110
+ flg += DISCARD if @discard
111
+ flg += OVERRIDE if @override
112
+ flg
113
+ end
114
+
115
+ def set_flag(flag)
116
+ flag = flag.to_i
117
+ @use = true if flag & USE > 0
118
+ @secure = true if flag & SECURE > 0
119
+ @domain_orig = true if flag & DOMAIN > 0
120
+ @path_orig = true if flag & PATH > 0
121
+ @discard = true if flag & DISCARD > 0
122
+ @override = true if flag & OVERRIDE > 0
123
+ end
124
+
125
+ def match?(url)
126
+ domainname = url.host
127
+ if (!domainname ||
128
+ !domain_match(domainname, @domain) ||
129
+ (@path && !head_match?(@path, url.path)) ||
130
+ (@secure && (url.scheme != 'https')) )
131
+ return false
132
+ else
133
+ return true
134
+ end
135
+ end
136
+
137
+ def join_quotedstr(array, sep)
138
+ ret = Array.new()
139
+ old_elem = nil
140
+ array.each{|elem|
141
+ if (elem.scan(/"/).length % 2) == 0
142
+ if old_elem
143
+ old_elem << sep << elem
144
+ else
145
+ ret << elem
146
+ old_elem = nil
147
+ end
148
+ else
149
+ if old_elem
150
+ old_elem << sep << elem
151
+ ret << old_elem
152
+ old_elem = nil
153
+ else
154
+ old_elem = elem.dup
155
+ end
156
+ end
157
+ }
158
+ ret
159
+ end
160
+
161
+ def parse(str, url)
162
+ @url = url
163
+ # TODO: should not depend on join_quotedstr. scan with escape like CSV.
164
+ cookie_elem = str.split(/;/)
165
+ cookie_elem = join_quotedstr(cookie_elem, ';')
166
+ cookie_elem -= [""] # del empty elements, a cookie might included ";;"
167
+ first_elem = cookie_elem.shift
168
+ if first_elem !~ /([^=]*)(\=(.*))?/
169
+ return
170
+ ## raise ArgumentError 'invalid cookie value'
171
+ end
172
+ @name = $1.strip
173
+ @value = normalize_cookie_value($3)
174
+ cookie_elem.each{|pair|
175
+ key, value = pair.split(/=/, 2) ## value may nil
176
+ key.strip!
177
+ value = normalize_cookie_value(value)
178
+ case key.downcase
179
+ when 'domain'
180
+ @domain = value
181
+ when 'expires'
182
+ @expires = nil
183
+ begin
184
+ @expires = Time.parse(value).gmtime() if value
185
+ rescue ArgumentError
186
+ end
187
+ when 'path'
188
+ @path = value
189
+ when 'secure'
190
+ @secure = true ## value may nil, but must 'true'.
191
+ else
192
+ ## ignore
193
+ end
194
+ }
195
+ end
196
+
197
+ def normalize_cookie_value(value)
198
+ if value
199
+ value = value.strip.sub(/\A"(.*)"\z/) { $1 }
200
+ value = nil if value.empty?
201
+ end
202
+ value
203
+ end
204
+ private :normalize_cookie_value
205
+ end
206
+
207
+ class CookieManager
208
+ include CookieUtils
209
+
210
+ ### errors
211
+ class Error < StandardError; end
212
+ class ErrorOverrideOK < Error; end
213
+ class SpecialError < Error; end
214
+
215
+ attr_reader :cookies
216
+ attr_accessor :cookies_file
217
+ attr_accessor :accept_domains, :reject_domains
218
+
219
+ # for conformance to http://wp.netscape.com/newsref/std/cookie_spec.html
220
+ attr_accessor :netscape_rule
221
+ SPECIAL_DOMAIN = [".com",".edu",".gov",".mil",".net",".org",".int"]
222
+
223
+ def initialize(file=nil)
224
+ @cookies = Array.new()
225
+ @cookies.extend(MonitorMixin)
226
+ @cookies_file = file
227
+ @is_saved = true
228
+ @reject_domains = Array.new()
229
+ @accept_domains = Array.new()
230
+ @netscape_rule = false
231
+ end
232
+
233
+ def cookies=(cookies)
234
+ @cookies = cookies
235
+ @cookies.extend(MonitorMixin)
236
+ end
237
+
238
+ def save_all_cookies(force = nil, save_unused = true, save_discarded = true)
239
+ @cookies.synchronize do
240
+ check_expired_cookies()
241
+ if @is_saved and !force
242
+ return
243
+ end
244
+ File.open(@cookies_file, 'w') do |f|
245
+ @cookies.each do |cookie|
246
+ if (cookie.use? or save_unused) and
247
+ (!cookie.discard? or save_discarded)
248
+ f.print(cookie.url.to_s,"\t",
249
+ cookie.name,"\t",
250
+ cookie.value,"\t",
251
+ cookie.expires.to_i,"\t",
252
+ cookie.domain,"\t",
253
+ cookie.path,"\t",
254
+ cookie.flag,"\n")
255
+ end
256
+ end
257
+ end
258
+ end
259
+ @is_saved = true
260
+ end
261
+
262
+ def save_cookies(force = nil)
263
+ save_all_cookies(force, false, false)
264
+ end
265
+
266
+ def check_expired_cookies()
267
+ @cookies.reject!{|cookie|
268
+ is_expired = (cookie.expires && (cookie.expires < Time.now.gmtime))
269
+ if is_expired && !cookie.discard?
270
+ @is_saved = false
271
+ end
272
+ is_expired
273
+ }
274
+ end
275
+
276
+ def parse(str, url)
277
+ cookie = WebAgent::Cookie.new()
278
+ cookie.parse(str, url)
279
+ add(cookie)
280
+ end
281
+
282
+ def make_cookie_str(cookie_list)
283
+ if cookie_list.empty?
284
+ return nil
285
+ end
286
+
287
+ ret = ''
288
+ c = cookie_list.shift
289
+ ret += "#{c.name}=#{c.value}"
290
+ cookie_list.each{|cookie|
291
+ ret += "; #{cookie.name}=#{cookie.value}"
292
+ }
293
+ return ret
294
+ end
295
+ private :make_cookie_str
296
+
297
+
298
+ def find(url)
299
+ return nil if @cookies.empty?
300
+
301
+ cookie_list = Array.new()
302
+ @cookies.each{|cookie|
303
+ is_expired = (cookie.expires && (cookie.expires < Time.now.gmtime))
304
+ if cookie.use? && !is_expired && cookie.match?(url)
305
+ if cookie_list.select{|c1| c1.name == cookie.name}.empty?
306
+ cookie_list << cookie
307
+ end
308
+ end
309
+ }
310
+ return make_cookie_str(cookie_list)
311
+ end
312
+
313
+ def find_cookie_info(domain, path, name)
314
+ @cookies.find{|c|
315
+ c.domain == domain && c.path == path && c.name == name
316
+ }
317
+ end
318
+ private :find_cookie_info
319
+
320
+ # not tested well; used only netscape_rule = true.
321
+ def cookie_error(err, override)
322
+ if !err.kind_of?(ErrorOverrideOK) || !override
323
+ raise err
324
+ end
325
+ end
326
+ private :cookie_error
327
+
328
+ def add(cookie)
329
+ url = cookie.url
330
+ name, value = cookie.name, cookie.value
331
+ expires, domain, path =
332
+ cookie.expires, cookie.domain, cookie.path
333
+ secure, domain_orig, path_orig =
334
+ cookie.secure?, cookie.domain_orig?, cookie.path_orig?
335
+ discard, override =
336
+ cookie.discard?, cookie.override?
337
+
338
+ domainname = url.host
339
+ domain_orig, path_orig = domain, path
340
+ use_security = override
341
+
342
+ if domain
343
+
344
+ # [DRAFT 12] s. 4.2.2 (does not apply in the case that
345
+ # host name is the same as domain attribute for version 0
346
+ # cookie)
347
+ # I think that this rule has almost the same effect as the
348
+ # tail match of [NETSCAPE].
349
+ if domain !~ /^\./ && domainname != domain
350
+ domain = '.'+domain
351
+ end
352
+
353
+ # [NETSCAPE] rule
354
+ if @netscape_rule
355
+ n = total_dot_num(domain)
356
+ if n < 2
357
+ cookie_error(SpecialError.new(), override)
358
+ elsif n == 2
359
+ ## [NETSCAPE] rule
360
+ ok = SPECIAL_DOMAIN.select{|sdomain|
361
+ sdomain == domain[-(sdomain.length)..-1]
362
+ }
363
+ if ok.empty?
364
+ cookie_error(SpecialError.new(), override)
365
+ end
366
+ end
367
+ end
368
+
369
+ # this implementation does not check RFC2109 4.3.2 case 2;
370
+ # the portion of host not in domain does not contain a dot.
371
+ # according to nsCookieService.cpp in Firefox 3.0.4, Firefox 3.0.4
372
+ # and IE does not check, too.
373
+ end
374
+
375
+ path ||= url.path.sub(%r|/[^/]*|, '')
376
+ domain ||= domainname
377
+ @cookies.synchronize do
378
+ cookie = find_cookie_info(domain, path, name)
379
+ if !cookie
380
+ cookie = WebAgent::Cookie.new()
381
+ cookie.use = true
382
+ @cookies << cookie
383
+ end
384
+ check_expired_cookies()
385
+ end
386
+
387
+ cookie.url = url
388
+ cookie.name = name
389
+ cookie.value = value
390
+ cookie.expires = expires
391
+ cookie.domain = domain
392
+ cookie.path = path
393
+
394
+ ## for flag
395
+ cookie.secure = secure
396
+ cookie.domain_orig = domain_orig
397
+ cookie.path_orig = path_orig
398
+ if discard || cookie.expires == nil
399
+ cookie.discard = true
400
+ else
401
+ cookie.discard = false
402
+ @is_saved = false
403
+ end
404
+ end
405
+
406
+ def load_cookies()
407
+ return if !File.readable?(@cookies_file)
408
+ @cookies.synchronize do
409
+ @cookies.clear
410
+ File.open(@cookies_file,'r'){|f|
411
+ while line = f.gets
412
+ cookie = WebAgent::Cookie.new()
413
+ @cookies << cookie
414
+ col = line.chomp.split(/\t/)
415
+ cookie.url = URI.parse(col[0])
416
+ cookie.name = col[1]
417
+ cookie.value = col[2]
418
+ if col[3].empty? or col[3] == '0'
419
+ cookie.expires = nil
420
+ else
421
+ cookie.expires = Time.at(col[3].to_i).gmtime
422
+ end
423
+ cookie.domain = col[4]
424
+ cookie.path = col[5]
425
+ cookie.set_flag(col[6])
426
+ end
427
+ }
428
+ end
429
+ end
430
+
431
+ def check_cookie_accept_domain(domain)
432
+ unless domain
433
+ return false
434
+ end
435
+ @accept_domains.each{|dom|
436
+ if domain_match(domain, dom)
437
+ return true
438
+ end
439
+ }
440
+ @reject_domains.each{|dom|
441
+ if domain_match(domain, dom)
442
+ return false
443
+ end
444
+ }
445
+ return true
446
+ end
447
+ end
448
+ end
449
+
450
+ __END__
451
+
452
+ =begin
453
+
454
+ == WebAgent::CookieManager Class
455
+
456
+ Load, save, parse and send cookies.
457
+
458
+ === Usage
459
+
460
+ ## initialize
461
+ cm = WebAgent::CookieManager.new("/home/foo/bar/cookie")
462
+
463
+ ## load cookie data
464
+ cm.load_cookies()
465
+
466
+ ## parse cookie from string (maybe "Set-Cookie:" header)
467
+ cm.parse(str)
468
+
469
+ ## send cookie data to url
470
+ f.write(cm.find(url))
471
+
472
+ ## save cookie to cookiefile
473
+ cm.save_cookies()
474
+
475
+
476
+ === Class Methods
477
+
478
+ -- CookieManager::new(file=nil)
479
+
480
+ create new CookieManager. If a file is provided,
481
+ use it as cookies' file.
482
+
483
+ === Methods
484
+
485
+ -- CookieManager#save_cookies(force = nil)
486
+
487
+ save cookies' data into file. if argument is true,
488
+ save data although data is not modified.
489
+
490
+ -- CookieManager#parse(str, url)
491
+
492
+ parse string and store cookie (to parse HTTP response header).
493
+
494
+ -- CookieManager#find(url)
495
+
496
+ get cookies and make into string (to send as HTTP request header).
497
+
498
+ -- CookieManager#add(cookie)
499
+
500
+ add new cookie.
501
+
502
+ -- CookieManager#load_cookies()
503
+
504
+ load cookies' data from file.
505
+
506
+
507
+ == WebAgent::CookieUtils Module
508
+
509
+ -- CookieUtils::head_match?(str1, str2)
510
+ -- CookieUtils::tail_match?(str1, str2)
511
+ -- CookieUtils::domain_match(host, domain)
512
+ -- CookieUtils::total_dot_num(str)
513
+
514
+
515
+ == WebAgent::Cookie Class
516
+
517
+ === Class Methods
518
+
519
+ -- Cookie::new()
520
+
521
+ create new cookie.
522
+
523
+ === Methods
524
+
525
+ -- Cookie#match?(url)
526
+
527
+ match cookie by url. if match, return true. otherwise,
528
+ return false.
529
+
530
+ -- Cookie#name
531
+ -- Cookie#name=(name)
532
+ -- Cookie#value
533
+ -- Cookie#value=(value)
534
+ -- Cookie#domain
535
+ -- Cookie#domain=(domain)
536
+ -- Cookie#path
537
+ -- Cookie#path=(path)
538
+ -- Cookie#expires
539
+ -- Cookie#expires=(expires)
540
+ -- Cookie#url
541
+ -- Cookie#url=(url)
542
+
543
+ accessor methods for cookie's items.
544
+
545
+ -- Cookie#discard?
546
+ -- Cookie#discard=(discard)
547
+ -- Cookie#use?
548
+ -- Cookie#use=(use)
549
+ -- Cookie#secure?
550
+ -- Cookie#secure=(secure)
551
+ -- Cookie#domain_orig?
552
+ -- Cookie#domain_orig=(domain_orig)
553
+ -- Cookie#path_orig?
554
+ -- Cookie#path_orig=(path_orig)
555
+ -- Cookie#override?
556
+ -- Cookie#override=(override)
557
+ -- Cookie#flag
558
+ -- Cookie#set_flag(flag_num)
559
+
560
+ accessor methods for flags.
561
+
562
+ =end
@@ -0,0 +1,106 @@
1
+ module Crusher
2
+ class CrushSession
3
+
4
+ attr_reader :target, :scenario, :log_file
5
+
6
+ def initialize(target, scenario, options = {})
7
+ @target = target
8
+ @scenario = scenario
9
+ @queued_jobs = []
10
+ @running_jobs = []
11
+ @options = options
12
+ @current_phase = "booting"
13
+
14
+ @scenario.launch_jobs.each do |job|
15
+ job_id = 0
16
+ puts job.keys.inspect
17
+ puts "Launching #{job[:count]} #{job[:type]}(s)..."
18
+ job[:count].times do
19
+ queue_job(job, job_id)
20
+ job_id += 1
21
+ end
22
+ end
23
+
24
+ launch_queued_jobs
25
+
26
+ end
27
+
28
+ def log(message)
29
+ log_message = Time.now.strftime('%Y-%m-%d %H:%M:%S') + ' ' + @current_phase + ' ' + message + "\n"
30
+ if @log_file
31
+ @log_file.syswrite(log_message)
32
+ else
33
+ puts log_message
34
+ end
35
+ nil
36
+ end
37
+
38
+ private
39
+
40
+ def queue_job(job, id)
41
+ @queued_jobs << [job, id]
42
+ end
43
+
44
+ def run_next_phase
45
+ if @scenario.phases.length == 0
46
+ shutdown
47
+ else
48
+ next_phase = @scenario.phases.shift
49
+ @current_phase = next_phase[:name].to_s
50
+
51
+ if next_phase[:wait]
52
+ EM::Timer.new(next_phase[:wait].to_i) do
53
+ run_next_phase
54
+ end
55
+ elsif next_phase[:over]
56
+ seconds_per_job = next_phase[:over].to_f / @running_jobs.count.to_f
57
+ next_job = 0
58
+
59
+ slow_iterator = EM::PeriodicTimer.new(seconds_per_job) do
60
+ next_phase[:proc].call(@running_jobs[next_job])
61
+ next_job += 1
62
+
63
+ if next_job >= @running_jobs.length
64
+ slow_iterator.cancel
65
+ run_next_phase
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+
73
+ def launch_queued_jobs
74
+ return nil if @queued_jobs.length == 0
75
+
76
+ fork do
77
+
78
+ @log_file = File.open(@options.delete(:log_file), 'a') if @options[:log_file]
79
+
80
+ # Reseed the pseudorandom number generator with the process's PID file, otherwise
81
+ # we'll inherit our sequence of random numbers from the parent process
82
+ Kernel.srand(Process.pid)
83
+
84
+ EM.run do
85
+ @queued_jobs.each do |settings|
86
+ job, job_id = settings
87
+ options = @options.merge(:process_name => "#{job[:type]} #{job_id}")
88
+ @running_jobs << job[:proc].call(self, options, job_id)
89
+ end
90
+ run_next_phase
91
+ end
92
+
93
+ end
94
+
95
+ @queued_jobs = []
96
+
97
+ nil
98
+ end
99
+
100
+ def shutdown
101
+ EM::stop_event_loop
102
+ end
103
+
104
+ end
105
+
106
+ end
@@ -0,0 +1,54 @@
1
+ module Crusher
2
+
3
+ class HttpLoadGenerator < LoadGenerator
4
+
5
+
6
+ def initialize(crush_session, options)
7
+ super(crush_session, options)
8
+ @cookies = ::WebAgent::CookieManager.new
9
+ end
10
+
11
+ def request(method, path, content = {}, &block)
12
+
13
+ uri = URI.parse(path)
14
+
15
+ http_args = content.keys.map do |key|
16
+ content[key] ? "#{URI.escape(key.to_s)}=#{URI.escape(content[key].to_s)}" : nil
17
+ end
18
+
19
+ qs, content = if method == :get
20
+ [((uri.query || '').split('&') + http_args.compact).join("&"), nil]
21
+ else
22
+ [uri.query, http_args.join("&")]
23
+ end
24
+
25
+ options = {
26
+ :verb => method,
27
+ :host => uri.host,
28
+ :port => uri.port,
29
+ :request => uri.path,
30
+ :query_string => qs,
31
+ :content => content,
32
+ :contenttype => 'application/x-www-form-urlencoded',
33
+ :cookie => @cookies.find(uri),
34
+ :ssl => (uri.scheme == "https" ? true : false)
35
+ }
36
+
37
+ start_time = Time.now
38
+ EM::Protocols::HttpClient.request(options).callback do |response|
39
+ duration = Time.now - start_time
40
+
41
+ response[:headers].each do |header|
42
+ matcher = /Set-Cookie:\s*(.*)/i.match(header)
43
+ @cookies.parse(matcher[1], uri) if matcher
44
+ end
45
+
46
+ block.call(response, duration) if block
47
+ end
48
+
49
+ end
50
+
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,25 @@
1
+ module Crusher
2
+
3
+ class LoadGenerator
4
+
5
+ def initialize(crush_session, options)
6
+ @crush_session = crush_session
7
+ @options = options
8
+ begin
9
+ @log_file = File.open(options[:log_file], 'a') if options[:log_file]
10
+ rescue Errno::ENOENT
11
+ log("Log file #{options[:log_file]} couldn't be opened; logging to STDOUT")
12
+ end
13
+ end
14
+
15
+ def act!; end
16
+
17
+ def prepare; end
18
+
19
+ def log(message)
20
+ @crush_session.log(message)
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,14 @@
1
+ module Crusher::Util
2
+ DUMMY_WORDS = %w( Vivamus condimentum mi vel massa blandit ut pretium risus cursus Pellentesque eu mollis turpis Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus Phasellus lobortis bibendum lectus Suspendisse suscipit sem in arcu tempor at malesuada lectus facilisis Sed euismod pretium gravida Ut feugiat tortor a erat mattis a eleifend est rutrum Ut est elit accumsan ut lacinia ac dignissim tincidunt elit Duis nisi mauris ornare eget laoreet vel euismod at sem Donec in neque nec odio ornare vestibulum Pellentesque ut turpis erat nec sollicitudin nulla Nulla varius fringilla porta Fusce sit amet orci eget sem iaculis porttitor Proin pulvinar sodales odio in dapibus erat viverra in Donec fringilla lacus et lacus tincidunt ut laoreet dolor tristique Nullam vulputate iaculis condimentum Mauris et ligula id erat auctor adipiscing Nulla luctus interdum consequatProin vehicula egestas leo a feugiat velit fermentum quis Morbi mattis dignissim tincidunt Nunc sed dignissim leo Mauris vitae mi sollicitudin eros elementum interdum non quis velit Nam eleifend aliquam lobortis Maecenas in metus non libero porttitor tincidunt vitae molestie augue Lorem ipsum dolor sit amet consectetur adipiscing elit Vestibulum blandit erat ac urna gravida ac aliquam nulla scelerisque Fusce ante purus rhoncus auctor molestie vel tincidunt sit amet orci Sed viverra augue ut quam condimentum dictum Duis ullamcorper elit in erat ullamcorper eget rhoncus velit blandit Morbi ultrices lacus id suscipit tempor turpis dolor placerat ante tempor mollis turpis leo et augue Phasellus augue dolor ultrices luctus rhoncus nec sollicitudin sit amet nequeVestibulum interdum nibh sed mauris vulputate sed tincidunt magna rhoncus Etiam sapien metus ultricies in eleifend a aliquet eget nibh Duis in est nibh eu tincidunt enim Vivamus suscipit felis a velit dignissim et fringilla mi laoreet Duis metus odio porta vitae dictum in rutrum ut quam Donec tristique adipiscing nisl non iaculis Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas Nulla eget pulvinar nulla Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus Donec venenatis justo eu vulputate venenatis leo ante varius enim ac laoreet lacus elit id ipsum Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas Etiam euismod lorem quis faucibus vehicula nisi lorem mattis felis in imperdiet urna magna non dolor Aenean sed eros eu odio pretium blandit In hac habitasse platea dictumst Vestibulum nisi orci malesuada eu adipiscing eget posuere nec orci Curabitur bibendum orci non felis faucibus lobortisSuspendisse imperdiet pellentesque magna sit amet commodo odio hendrerit vitae Duis dapibus tempor nunc interdum vulputate Curabitur fringilla erat ut magna viverra pulvinar sed eu nunc Phasellus sit amet aliquam nulla Donec aliquam nibh in ullamcorper dictum nibh risus pellentesque nunc eu laoreet nulla elit sed justo Donec blandit justo eu mi ultricies eu tempus turpis fringilla Proin ligula elit varius vel egestas et hendrerit sed metus Phasellus nec tortor nunc eu auctor quam Nulla quis ultricies felis Proin iaculis felis non ultrices )
3
+
4
+ srand Time.now.seconds # Seeds rng on boot
5
+
6
+ def self.nrand(min, max)
7
+ (1..6).map{ rand(min,max) }.sum / 6
8
+ end
9
+
10
+ def self.lipsum(words)
11
+ (1..words).map{ DUMMY_WORDS[ rand(DUMMY_WORDS.length) ] }.join(' ')
12
+ end
13
+
14
+ end
data/lib/crusher.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'crusher/crush_session'
3
+ require 'crusher/load_generator'
4
+ require 'crusher/cookie'
5
+ require 'crusher/http_load_generator'
6
+ require 'crusher/util'
7
+ require 'crusher/configurator'
8
+
9
+
10
+ module Crusher
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crusher
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - phene
14
+ - thatothermitch
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-11-08 00:00:00 -08:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: httpclient
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: crusher-eventmachine
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ description: A scriptable, multiprocessing load generator designed to bring web applications to their knees
51
+ email:
52
+ executables:
53
+ - crush
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - lib/crusher/configurator.rb
60
+ - lib/crusher/cookie.rb
61
+ - lib/crusher/crush_session.rb
62
+ - lib/crusher/http_load_generator.rb
63
+ - lib/crusher/load_generator.rb
64
+ - lib/crusher/util.rb
65
+ - lib/crusher.rb
66
+ - bin/crush
67
+ - Rakefile
68
+ has_rdoc: false
69
+ homepage: http://github.com/socialcast/crusher
70
+ licenses: []
71
+
72
+ post_install_message:
73
+ rdoc_options: []
74
+
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ hash: 3
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ requirements: []
96
+
97
+ rubyforge_project:
98
+ rubygems_version: 1.3.7
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: A scriptable load generator
102
+ test_files: []
103
+