stella 0.7.0.006 → 0.7.0.012

Sign up to get free protection for your applications and to get access to all the features.
data/lib/stella/stats.rb CHANGED
@@ -1,12 +1,11 @@
1
1
 
2
2
  module Stella
3
3
  # Based on Mongrel::Stats, Copyright (c) 2005 Zed A. Shaw
4
- class Stats
4
+ class Stats < Array
5
5
 
6
6
  attr_reader :sum, :sumsq, :n, :min, :max
7
7
 
8
- def initialize(name)
9
- @name = name
8
+ def initialize
10
9
  reset
11
10
  end
12
11
 
@@ -16,13 +15,19 @@ class Stats
16
15
 
17
16
  # Resets the internal counters so you can start sampling again.
18
17
  def reset
18
+ self.clear
19
19
  @n, @sum, @sumsq = 0.0, 0.0, 0.0
20
- @last_time = Time.new
20
+ @last_time = 0.0
21
21
  @min, @max = 0.0, 0.0
22
22
  end
23
23
 
24
24
  # Adds a sampling to the calculations.
25
25
  def sample(s)
26
+ self << s
27
+ update s
28
+ end
29
+
30
+ def update(s)
26
31
  @sum += s
27
32
  @sumsq += s * s
28
33
  if @n == 0
@@ -33,7 +38,7 @@ class Stats
33
38
  end
34
39
  @n+=1
35
40
  end
36
-
41
+
37
42
  # Dump this Stats object with an optional additional message.
38
43
  def dump(msg = "", out=STDERR)
39
44
  out.puts "#{msg}: #{self.inspect}"
@@ -42,26 +47,16 @@ class Stats
42
47
  # Returns a common display (used by dump)
43
48
  def inspect
44
49
  v = [mean, @n, @sum, @sumsq, sd, @min, @max]
45
- t = %w"N=%0.4f SUM=%0.4f SUMSQ=%0.4f SD=%0.4f MIN=%0.4f MAX=%0.4f"
46
- "%0.4f: " << t % v
50
+ t = %q"N=%0.4f SUM=%0.4f SUMSQ=%0.4f SD=%0.4f MIN=%0.4f MAX=%0.4f"
51
+ ("%0.4f: " << t) % v
47
52
  end
48
53
 
49
- def to_s
50
- mean.to_s
51
- end
52
-
53
- def to_f
54
- mean.to_f
55
- end
56
-
57
- def to_i
58
- mean.to_i
59
- end
54
+ def to_s; mean.to_s; end
55
+ def to_f; mean.to_f; end
56
+ def to_i; mean.to_i; end
60
57
 
61
58
  # Calculates and returns the mean for the data passed so far.
62
- def mean
63
- @sum / @n
64
- end
59
+ def mean; return 0.0 unless @n > 0; @sum / @n; end
65
60
 
66
61
  # Calculates the standard deviation of the data so far.
67
62
  def sd
@@ -73,21 +68,12 @@ class Stats
73
68
  return 0.0
74
69
  end
75
70
  end
76
-
77
-
78
- # Adds a time delta between now and the last time you called this. This
79
- # will give you the average time between two activities.
80
- #
81
- # An example is:
82
- #
83
- # t = Stats.new("do_stuff")
84
- # 10000.times { do_stuff(); t.tick }
85
- # t.dump("time")
86
- #
87
- def tick
88
- now = Time.now
89
- sample(now - @last_time)
90
- @last_time = now
71
+
72
+ def recalculate
73
+ samples = self.clone
74
+ reset
75
+ samples.each { |s| sample(s) }
91
76
  end
77
+
92
78
  end
93
79
  end
@@ -73,6 +73,8 @@ class Testplan
73
73
  req.desc = args[1] if args.size > 1 # Description is optional
74
74
  Stella.ld req
75
75
  @requests << req
76
+ req.gibbler
77
+ req.freeze
76
78
  req
77
79
  end
78
80
  def get(*args, &blk); add_request :get, *args, &blk; end
@@ -22,8 +22,9 @@ class Testplan
22
22
  usecase = Stella::Testplan::Usecase.new
23
23
  usecase.ratio = 1.0
24
24
  uris.each do |uri|
25
+ uri = 'http://' << uri unless uri.match /^http:\/\//i
25
26
  uri = URI.parse uri
26
- uri.path = '/' if uri.path.empty?
27
+ uri.path = '/' if uri.path.nil? || uri.path.empty?
27
28
  req = usecase.add_request :get, uri.path
28
29
  req.wait = opts[:delay] if opts[:delay]
29
30
  end
@@ -37,6 +38,8 @@ class Testplan
37
38
  plan.base_path = File.dirname path
38
39
  # eval so the DSL code can be executed in this namespace.
39
40
  plan.instance_eval conf
41
+ plan.gibbler
42
+ plan.freeze
40
43
  plan
41
44
  end
42
45
 
@@ -59,6 +62,10 @@ class Testplan
59
62
  msg << " (#{@testplan_current_ratio})"
60
63
  raise WackyRatio, msg
61
64
  end
65
+ @usecases.each do |uc|
66
+ uc.gibbler # make sure gibbler_cache has a value
67
+ uc.freeze # make sure all clients share identical usecases
68
+ end
62
69
  end
63
70
 
64
71
  def usecase(*args, &blk)
@@ -88,12 +95,14 @@ class Testplan
88
95
 
89
96
  def pretty
90
97
  str = []
91
- str << " %-66s ".att(:reverse) % [@desc]
98
+ str << " %-66s ".att(:reverse) % ["#{@desc} (#{self.gibbler_cache.shorter})"]
92
99
  @usecases.each_with_index do |uc,i|
93
- description = uc.desc || "Usecase ##{i+1}"
94
- str << " %s (%s%%)".bright % [description, uc.ratio_pretty]
100
+ desc = uc.desc || "Usecase ##{i+1}"
101
+ desc += " (#{uc.gibbler_cache.shorter}) "
102
+ str << (' ' << " %-61s %s%% ".att(:reverse).bright) % [desc, uc.ratio_pretty]
95
103
  requests = uc.requests.each do |r|
96
- str << " %-35s %s" % ["#{r.desc}:", r]
104
+ str << " %-62s".bright % ["#{r.desc} (#{r.gibbler_cache.shorter})"]
105
+ str << " %s" % [r]
97
106
  if Stella.loglev > 2
98
107
  [:wait].each { |i| str << " %s: %s" % [i, r.send(i)] }
99
108
  end
@@ -6,7 +6,7 @@ module Stella
6
6
  MAJOR = 0.freeze
7
7
  MINOR = 7.freeze
8
8
  TINY = 0.freeze
9
- PATCH = '006'.freeze
9
+ PATCH = '012'.freeze
10
10
  end
11
11
  def self.to_s; [MAJOR, MINOR, TINY].join('.'); end
12
12
  def self.to_f; self.to_s.to_f; end
data/lib/stella.rb CHANGED
@@ -21,6 +21,7 @@ module Stella
21
21
  @@logger = Drydock::Screen
22
22
  @@loglev = 1
23
23
  @@debug = false
24
+ @@abort = false
24
25
 
25
26
  # Puts +msg+ to +@@logger+
26
27
  def lflush; @@logger.flush if @@logger.respond_to? :flush; end
@@ -49,6 +50,11 @@ module Stella
49
50
  def enable_debug; @@debug = true; end
50
51
  def disable_debug; @@debug = false; end
51
52
 
53
+ def abort?
54
+ @@abort == true
55
+ end
56
+ def abort!() @@abort = true end
57
+
52
58
  def rescue(&blk)
53
59
  blk.call
54
60
  rescue => ex
@@ -61,6 +67,7 @@ require 'stella/version'
61
67
  require 'stella/exceptions'
62
68
  require 'stella/utils'
63
69
  require 'stella/config'
70
+ require 'stella/data'
64
71
 
65
72
  Stella::Utils.require_vendor "httpclient", '2.1.5.2'
66
73
  Stella::Utils.require_glob(Stella::LIB_HOME, 'stella', '*.rb')
data/stella.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  @spec = Gem::Specification.new do |s|
2
2
  s.name = "stella"
3
3
  s.rubyforge_project = 'stella'
4
- s.version = "0.7.0.006"
4
+ s.version = "0.7.0.012"
5
5
  s.summary = "Stella: Perform load tests on your web applications with beauty and brute strength."
6
6
  s.description = s.summary
7
7
  s.author = "Delano Mandelbaum"
@@ -15,9 +15,9 @@
15
15
 
16
16
  s.executables = %w[stella]
17
17
 
18
- s.add_dependency 'benelux'
18
+ s.add_dependency 'benelux', '>= 0.3.2'
19
19
  s.add_dependency 'drydock', '>= 0.6.8'
20
- s.add_dependency 'gibbler', '>= 0.6.2'
20
+ s.add_dependency 'gibbler', '>= 0.6.3'
21
21
  s.add_dependency 'storable', '>= 0.5.7'
22
22
  s.add_dependency 'httpclient', '>= 2.1.5'
23
23
  s.add_dependency 'nokogiri'
@@ -30,6 +30,7 @@
30
30
  README.rdoc
31
31
  Rakefile
32
32
  bin/stella
33
+ examples/cookies/plan.rb
33
34
  examples/essentials/logo.png
34
35
  examples/essentials/plan.rb
35
36
  examples/essentials/search_terms.csv
@@ -39,6 +40,8 @@
39
40
  lib/stella.rb
40
41
  lib/stella/cli.rb
41
42
  lib/stella/client.rb
43
+ lib/stella/client/container.rb
44
+ lib/stella/client/modifiers.rb
42
45
  lib/stella/config.rb
43
46
  lib/stella/data.rb
44
47
  lib/stella/data/http.rb
@@ -51,6 +54,7 @@
51
54
  lib/stella/exceptions.rb
52
55
  lib/stella/guidelines.rb
53
56
  lib/stella/mixins.rb
57
+ lib/stella/mixins/thread.rb
54
58
  lib/stella/stats.rb
55
59
  lib/stella/testplan.rb
56
60
  lib/stella/testplan/stats.rb
@@ -70,7 +74,6 @@
70
74
  vendor/httpclient-2.1.5.2/httpclient/http.rb
71
75
  vendor/httpclient-2.1.5.2/httpclient/session.rb
72
76
  vendor/httpclient-2.1.5.2/httpclient/ssl_config.rb
73
- vendor/httpclient-2.1.5.2/httpclient/stats.rb
74
77
  vendor/httpclient-2.1.5.2/httpclient/timeout.rb
75
78
  vendor/httpclient-2.1.5.2/httpclient/util.rb
76
79
  )
@@ -25,9 +25,9 @@ class WebAgent
25
25
 
26
26
  def tail_match?(str1, str2)
27
27
  if str1.length > 0
28
- str1 == str2[-str1.length..-1].to_s
28
+ str1 == str2[-str1.length..-1].to_s
29
29
  else
30
- true
30
+ true
31
31
  end
32
32
  end
33
33
 
@@ -36,14 +36,14 @@ class WebAgent
36
36
  hostname = host.sub(/\.\z/, '').downcase
37
37
  case domain
38
38
  when /\d+\.\d+\.\d+\.\d+/
39
- return (hostname == domainname)
39
+ return (hostname == domainname)
40
40
  when '.'
41
- return true
41
+ return true
42
42
  when /^\./
43
43
  # allows; host == rubyforge.org, domain == .rubyforge.org
44
- return tail_match?(domainname, '.' + hostname)
44
+ return tail_match?(domainname, '.' + hostname)
45
45
  else
46
- return (hostname == domainname)
46
+ return (hostname == domainname)
47
47
  end
48
48
  end
49
49
 
@@ -125,12 +125,12 @@ class WebAgent
125
125
  def match?(url)
126
126
  domainname = url.host
127
127
  if (!domainname ||
128
- !domain_match(domainname, @domain) ||
129
- (@path && !head_match?(@path, url.path)) ||
130
- (@secure && (url.scheme != 'https')) )
131
- return false
128
+ !domain_match(domainname, @domain) ||
129
+ (@path && !head_match?(@path, url.path)) ||
130
+ (@secure && (url.scheme != 'https')) )
131
+ return false
132
132
  else
133
- return true
133
+ return true
134
134
  end
135
135
  end
136
136
 
@@ -138,22 +138,22 @@ class WebAgent
138
138
  ret = Array.new()
139
139
  old_elem = nil
140
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
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
157
  }
158
158
  ret
159
159
  end
@@ -166,31 +166,31 @@ class WebAgent
166
166
  cookie_elem -= [""] # del empty elements, a cookie might included ";;"
167
167
  first_elem = cookie_elem.shift
168
168
  if first_elem !~ /([^=]*)(\=(.*))?/
169
- return
170
- ## raise ArgumentError 'invalid cookie value'
169
+ return
170
+ ## raise ArgumentError 'invalid cookie value'
171
171
  end
172
172
  @name = $1.strip
173
173
  @value = normalize_cookie_value($3)
174
174
  cookie_elem.each{|pair|
175
- key, value = pair.split(/=/, 2) ## value may nil
176
- key.strip!
175
+ key, value = pair.split(/=/, 2) ## value may nil
176
+ key.strip!
177
177
  value = normalize_cookie_value(value)
178
- case key.downcase
179
- when 'domain'
180
- @domain = value
181
- when 'expires'
178
+ case key.downcase
179
+ when 'domain'
180
+ @domain = value
181
+ when 'expires'
182
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
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
194
  }
195
195
  end
196
196
 
@@ -281,14 +281,14 @@ class WebAgent
281
281
 
282
282
  def make_cookie_str(cookie_list)
283
283
  if cookie_list.empty?
284
- return nil
284
+ return nil
285
285
  end
286
286
 
287
287
  ret = ''
288
288
  c = cookie_list.shift
289
289
  ret += "#{c.name}=#{c.value}"
290
290
  cookie_list.each{|cookie|
291
- ret += "; #{cookie.name}=#{cookie.value}"
291
+ ret += "; #{cookie.name}=#{cookie.value}"
292
292
  }
293
293
  return ret
294
294
  end
@@ -312,7 +312,7 @@ class WebAgent
312
312
 
313
313
  def find_cookie_info(domain, path, name)
314
314
  @cookies.find{|c|
315
- c.domain == domain && c.path == path && c.name == name
315
+ c.domain == domain && c.path == path && c.name == name
316
316
  }
317
317
  end
318
318
  private :find_cookie_info
@@ -320,7 +320,7 @@ class WebAgent
320
320
  # not tested well; used only netscape_rule = true.
321
321
  def cookie_error(err, override)
322
322
  if !err.kind_of?(ErrorOverrideOK) || !override
323
- raise err
323
+ raise err
324
324
  end
325
325
  end
326
326
  private :cookie_error
@@ -329,11 +329,11 @@ class WebAgent
329
329
  url = cookie.url
330
330
  name, value = cookie.name, cookie.value
331
331
  expires, domain, path =
332
- cookie.expires, cookie.domain, cookie.path
332
+ cookie.expires, cookie.domain, cookie.path
333
333
  secure, domain_orig, path_orig =
334
- cookie.secure?, cookie.domain_orig?, cookie.path_orig?
334
+ cookie.secure?, cookie.domain_orig?, cookie.path_orig?
335
335
  discard, override =
336
- cookie.discard?, cookie.override?
336
+ cookie.discard?, cookie.override?
337
337
 
338
338
  domainname = url.host
339
339
  domain_orig, path_orig = domain, path
@@ -341,14 +341,14 @@ class WebAgent
341
341
 
342
342
  if domain
343
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
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
352
 
353
353
  # [NETSCAPE] rule
354
354
  if @netscape_rule
@@ -396,10 +396,10 @@ class WebAgent
396
396
  cookie.domain_orig = domain_orig
397
397
  cookie.path_orig = path_orig
398
398
  if discard || cookie.expires == nil
399
- cookie.discard = true
399
+ cookie.discard = true
400
400
  else
401
- cookie.discard = false
402
- @is_saved = false
401
+ cookie.discard = false
402
+ @is_saved = false
403
403
  end
404
404
  end
405
405
 
@@ -430,17 +430,17 @@ class WebAgent
430
430
 
431
431
  def check_cookie_accept_domain(domain)
432
432
  unless domain
433
- return false
433
+ return false
434
434
  end
435
435
  @accept_domains.each{|dom|
436
- if domain_match(domain, dom)
437
- return true
438
- end
436
+ if domain_match(domain, dom)
437
+ return true
438
+ end
439
439
  }
440
440
  @reject_domains.each{|dom|
441
- if domain_match(domain, dom)
442
- return false
443
- end
441
+ if domain_match(domain, dom)
442
+ return false
443
+ end
444
444
  }
445
445
  return true
446
446
  end
@@ -780,7 +780,10 @@ module HTTP
780
780
 
781
781
  # HTTP::Message::Body:: message body.
782
782
  attr_reader :body
783
-
783
+
784
+ # Response only. Request object associated to this response.
785
+ attr_accessor :request
786
+
784
787
  # OpenSSL::X509::Certificate:: response only. server certificate which is
785
788
  # used for retrieving the response.
786
789
  attr_accessor :peer_cert
@@ -541,7 +541,7 @@ class HTTPClient
541
541
  raise
542
542
  end
543
543
  end
544
-
544
+
545
545
  @state = :META if @state == :WAIT
546
546
  @next_connection = nil
547
547
  @requests.push(req)
@@ -742,13 +742,13 @@ class HTTPClient
742
742
  end
743
743
 
744
744
  # Added by delano for Benelux support
745
- def socket_gets_initial_line(*args); @socket.gets(*args); end
745
+ def socket_gets_first_byte(*args); @socket.gets(*args); end
746
746
 
747
747
  StatusParseRegexp = %r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?\r?\n\z)
748
748
  def parse_header
749
749
  timeout(@receive_timeout, ReceiveTimeoutError) do
750
750
  begin
751
- initial_line = socket_gets_initial_line("\n")
751
+ initial_line = socket_gets_first_byte("\n")
752
752
  if initial_line.nil?
753
753
  raise KeepAliveDisconnected.new
754
754
  end
@@ -82,7 +82,8 @@ class HTTPClient
82
82
  @timeout = nil
83
83
  @options = defined?(SSL::OP_ALL) ? SSL::OP_ALL | SSL::OP_NO_SSLv2 : nil
84
84
  @ciphers = "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
85
- load_cacerts
85
+ # TODO: Load only once for all instances.
86
+ @cert_store = @@cacerts
86
87
  end
87
88
 
88
89
  # Sets certificate (OpenSSL::X509::Certificate) for SSL client
@@ -338,7 +339,7 @@ class HTTPClient
338
339
  @client.reset_all
339
340
  end
340
341
 
341
- def load_cacerts
342
+ def self.load_cacerts
342
343
  [
343
344
  [DIST_CERT, 'cacert.p7s'],
344
345
  [DIST_CERT_SHA1, 'cacert_sha1.p7s']
@@ -350,14 +351,15 @@ class HTTPClient
350
351
  store = X509::Store.new
351
352
  store.add_cert(selfcert)
352
353
  if (p7.verify(nil, store, p7.data, 0))
353
- set_trust_ca(file)
354
- return
354
+ #set_trust_ca(file)
355
+ store.add_file(file)
356
+ return store
355
357
  end
356
358
  end
357
359
  end
358
360
  STDERR.puts("cacerts loading failed")
359
361
  end
360
-
362
+
361
363
  DIST_CERT =<<__DIST_CERT__
362
364
  -----BEGIN CERTIFICATE-----
363
365
  MIID/TCCAuWgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBLMQswCQYDVQQGEwJKUDER
@@ -411,7 +413,9 @@ RwRyYoHysODGvnu8VXS1hGRr2GIxeBga7dAGa2VLE/iUQ0d4lEskYU+6C4ZLyAWF
411
413
  O89dvLNRzpL10MaWCYVREks=
412
414
  -----END CERTIFICATE-----
413
415
  __DIST_CERT__
414
- end
415
416
 
416
417
 
418
+ @@cacerts = load_cacerts
419
+ end
420
+
417
421
  end
@@ -19,7 +19,6 @@ require 'httpclient/session'
19
19
  require 'httpclient/http'
20
20
  require 'httpclient/auth'
21
21
  require 'httpclient/cookie'
22
- require 'httpclient/stats'
23
22
 
24
23
 
25
24
  # The HTTPClient class provides several methods for accessing Web resources
@@ -298,8 +297,6 @@ class HTTPClient
298
297
  # How many times get_content and post_content follows HTTP redirect.
299
298
  # 10 by default.
300
299
  attr_accessor :follow_redirect_count
301
- # A Timer object containing response times
302
- attr_reader :timer
303
300
 
304
301
  # Set HTTP version as a String:: 'HTTP/1.0' or 'HTTP/1.1'
305
302
  attr_proxy(:protocol_version, true)
@@ -357,7 +354,6 @@ class HTTPClient
357
354
  @session_manager.ssl_config = @ssl_config = SSLConfig.new(self)
358
355
  @cookie_manager = WebAgent::CookieManager.new
359
356
  @follow_redirect_count = 10
360
- @timer = Timer.new
361
357
  load_environment
362
358
  self.proxy = proxy if proxy
363
359
  end
@@ -770,12 +766,14 @@ private
770
766
  do_get_block(req, proxy, conn, &block)
771
767
  end
772
768
  res = conn.pop
769
+ res.request = req
773
770
  break
774
771
  rescue RetryableResponse
775
772
  res = conn.pop
776
773
  retry_count -= 1
777
774
  end
778
775
  end
776
+
779
777
  res
780
778
  end
781
779
 
@@ -956,6 +954,7 @@ private
956
954
  res = HTTP::Message.new_response(content)
957
955
  @debug_dev << "= Request\n\n" if @debug_dev
958
956
  sess = @session_manager.query(req, proxy)
957
+
959
958
  res.peer_cert = sess.ssl_peer_cert
960
959
  @debug_dev << "\n\n= Response\n\n" if @debug_dev
961
960
  do_get_header(req, res, sess)