stella 0.7.0.006 → 0.7.0.012

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/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)