stella 0.6.0 → 0.7.0.002

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.
Files changed (55) hide show
  1. data/CHANGES.txt +7 -15
  2. data/LICENSE.txt +1 -1
  3. data/README.rdoc +93 -63
  4. data/Rakefile +32 -42
  5. data/bin/stella +138 -0
  6. data/examples/basic/listing_ids.csv +7 -0
  7. data/examples/basic/plan.rb +71 -0
  8. data/lib/stella/cli.rb +66 -0
  9. data/lib/stella/client.rb +199 -0
  10. data/lib/stella/config.rb +87 -0
  11. data/lib/stella/data/http/body.rb +15 -0
  12. data/lib/stella/data/http/request.rb +116 -0
  13. data/lib/stella/data/http/response.rb +92 -0
  14. data/lib/stella/data/http.rb +2 -257
  15. data/lib/stella/data.rb +85 -0
  16. data/lib/stella/dsl.rb +5 -0
  17. data/lib/stella/engine/functional.rb +39 -0
  18. data/lib/stella/engine/load.rb +106 -0
  19. data/lib/stella/engine.rb +55 -0
  20. data/lib/stella/exceptions.rb +15 -0
  21. data/lib/stella/guidelines.rb +18 -0
  22. data/lib/stella/mixins.rb +2 -0
  23. data/lib/stella/stats.rb +3 -7
  24. data/lib/stella/testplan/stats.rb +26 -0
  25. data/lib/stella/testplan/usecase.rb +67 -0
  26. data/lib/stella/testplan.rb +95 -220
  27. data/lib/{util → stella/utils}/httputil.rb +0 -0
  28. data/lib/stella/utils.rb +126 -0
  29. data/lib/stella/version.rb +15 -0
  30. data/lib/stella.rb +58 -104
  31. data/lib/threadify.rb +0 -6
  32. data/stella.gemspec +43 -49
  33. data/support/example_webapp.rb +246 -0
  34. data/support/useragents.txt +75 -0
  35. metadata +68 -32
  36. data/bin/example_test.rb +0 -82
  37. data/bin/example_webapp.rb +0 -63
  38. data/lib/logger.rb +0 -79
  39. data/lib/stella/clients.rb +0 -161
  40. data/lib/stella/command/base.rb +0 -20
  41. data/lib/stella/command/form.rb +0 -36
  42. data/lib/stella/command/get.rb +0 -44
  43. data/lib/stella/common.rb +0 -53
  44. data/lib/stella/crypto.rb +0 -88
  45. data/lib/stella/data/domain.rb +0 -82
  46. data/lib/stella/environment.rb +0 -66
  47. data/lib/stella/functest.rb +0 -105
  48. data/lib/stella/loadtest.rb +0 -186
  49. data/lib/stella/testrunner.rb +0 -64
  50. data/lib/storable.rb +0 -280
  51. data/lib/timeunits.rb +0 -65
  52. data/tryouts/drb/drb_test.rb +0 -65
  53. data/tryouts/drb/open4.rb +0 -19
  54. data/tryouts/drb/slave.rb +0 -27
  55. data/tryouts/oo_tryout.rb +0 -30
@@ -1,259 +1,4 @@
1
1
 
2
- require 'storable'
3
- require 'util/httputil'
4
- require 'base64'
2
+ module Stella::Data::HTTP; end
5
3
 
6
- module Stella::Data
7
-
8
- # TODO: Implement HTTPHeaders. We should be printing untouched headers.
9
- # HTTPUtil should split the HTTP event lines and that's it. Replace
10
- # parse_header_body with split_header_body
11
- class HTTPHeaders < Storable
12
- attr_reader :raw_data
13
-
14
- def to_s
15
- @raw_data
16
- end
17
-
18
- end
19
-
20
- class HTTPBody < Storable
21
- field :content_type
22
- field :form_param
23
- field :content
24
-
25
- def has_content?
26
- !@content.nil?
27
- end
28
-
29
- end
30
-
31
- class HTTPRequest < Storable
32
- # A string representing a raw HTTP request
33
- attr_reader :raw_data
34
-
35
- # A hash containing blocks to be executed depending on the HTTP response status.
36
- # The hash keys are numeric HTTP Status Codes.
37
- #
38
- # 200 => { ... }
39
- # 304 => { ... }
40
- # 500 => { ... }
41
- #
42
- attr_accessor :response_handler
43
-
44
- field :name
45
- field :stella_id
46
- field :unique_id
47
- field :time => DateTime
48
- field :client_ip
49
- field :server_ip
50
- field :header
51
- field :uri
52
- field :params
53
- field :body
54
- field :http_method
55
- field :http_version
56
-
57
-
58
- def has_body?
59
- !@body.nil? && !@body.empty?
60
- end
61
- def has_request?
62
- false
63
- end
64
-
65
- def initialize (uri_str, method="GET", version="1.1")
66
- @uri = (uri_str.is_a? String) ? URI.parse(uri_str) : uri
67
- @http_method = method
68
- @http_version = version
69
- @headers = {}
70
- @params = {}
71
- @response_handler = {}
72
- @time = Time.now
73
- @stella_id = Stella::Crypto.sign(time.to_i.to_s, "#{@http_method}/#{@uri}/#{@params}")
74
- @unique_id = nil
75
- @body = HTTPBody.new
76
- end
77
-
78
- def set_unique_id(seasoning=rand)
79
- @unique_id = Stella::Crypto.sign(rand.to_s + seasoning.to_s, "#{@http_method}/#{@uri}/#{@params}")
80
- end
81
-
82
-
83
- def from_raw(raw_data=nil)
84
- @raw_data = raw_data
85
- @http_method, @http_version, @uri, @header, @body = self.parse(@raw_data)
86
- @time = DateTime.now
87
- end
88
-
89
- def self.parse(raw)
90
- return unless raw
91
- HTTPUtil::parse_http_request(raw, @uri.host, @uri.port)
92
- end
93
-
94
-
95
- def add_header(*args)
96
- name, value = (args[0].is_a? Hash) ? args[0].to_a.flatten : args
97
- @headers[name.to_s] ||= []
98
- @headers[name.to_s] << value
99
- end
100
- def add_param(*args)
101
- name, value = (args[0].is_a? Hash) ? args[0].to_a.flatten : args
102
-
103
- # BUG: This auto-array shit is causing a problem where the one request
104
- # will set the param and then next will set it again and it becomes
105
- # an array.
106
- #if @params[name.to_s] && !@params[name.to_s].is_a?(Array)
107
- # @params[name.to_s] = [@params[name.to_s]]
108
- #else
109
- # @params[name.to_s] = ""
110
- #end
111
-
112
- @params[name.to_s] = value.to_s
113
- end
114
-
115
- def add_response_handler(*args, &b)
116
- args << 200 if args.empty?
117
- args.each do |status|
118
- @response_handler[status] = b
119
- end
120
- end
121
-
122
- # +content+ can be literal content or a file path
123
- def add_body(content, form_param=nil, content_type=nil)
124
- @body = Stella::Data::HTTPBody.new
125
-
126
- @body.form_param = form_param if form_param
127
- @body.content_type = content_type if content_type
128
-
129
- if File.exists?(content)
130
- @body.content = File.new(content)
131
- @body.content_type ||= "application/x-www-form-urlencoded"
132
- else
133
- @body.content = content
134
- end
135
-
136
- end
137
-
138
-
139
- def body
140
- return nil unless @body
141
- @body
142
- #(!header || header[:Content_Type] || header[:Content_Type] !~ /text/) ? Base64.encode64(@body) : @body
143
- end
144
-
145
-
146
- def headers
147
- return [] unless header
148
- headers = []
149
- header.each_pair do |n,v|
150
- headers << [n.to_s.gsub('_', '-'), v[0]]
151
- end
152
- headers
153
- end
154
-
155
- def inspect
156
- str = "%s %s HTTP/%s" % [http_method, uri.to_s, http_version]
157
- str << $/ + headers.join($/) unless headers.empty?
158
- str << $/ + $/ + body.to_s if body
159
- str
160
- end
161
-
162
- def to_s
163
- str = "%s: %s %s HTTP/%s" % [time.strftime(NICE_TIME_FORMAT), http_method, uri.to_s, http_version]
164
- str
165
- end
166
-
167
- def cookies
168
- return [] if !header.is_a?(Hash) || header[:Cookie].empty?
169
- header[:Cookie]
170
- end
171
-
172
- end
173
-
174
- class HTTPResponse < Storable
175
- attr_reader :raw_data
176
-
177
- field :time => DateTime
178
- field :client_ip => String
179
- field :server_ip => String
180
- field :header => String
181
- field :body => String
182
- field :status => String
183
- field :message => String
184
- field :http_version => String
185
-
186
- def initialize(raw_data=nil)
187
- @raw_data = raw_data
188
- parse(@raw_data)
189
- end
190
-
191
- def parse(raw)
192
- return unless raw
193
- @status, @http_version, @message, @header, @body = HTTPUtil::parse_http_response(raw)
194
- end
195
-
196
- def has_body?
197
- !@body.nil? && !@body.empty?
198
- end
199
- def has_request?
200
- false
201
- end
202
- def has_response?
203
- false
204
- end
205
-
206
-
207
- def body
208
- return nil unless @body
209
- #TODO: Move to HTTPResponse::Body.to_s
210
- if is_binary?
211
- "[skipping binary content]"
212
- elsif is_gzip?
213
- #require 'zlib'
214
- #Zlib::Inflate.inflate(@body)
215
- "[skipping gzip content]"
216
- else
217
- @body
218
- end
219
- end
220
-
221
- def headers
222
- headers = []
223
- header.each_pair do |n,v|
224
- headers << [n.to_s.gsub('_', '-'), v[0]]
225
- end
226
- headers
227
- end
228
-
229
- def is_binary?
230
- (!is_text?) == true
231
- end
232
-
233
- def is_text?
234
- (!header[:Content_Type].nil? && (header[:Content_Type][0].is_a? String) && header[:Content_Type][0][/text/] != nil)
235
- end
236
-
237
- def is_gzip?
238
- (!header[:Content_Encoding].nil? && (header[:Content_Encoding][0].is_a? String) && header[:Content_Encoding][0][/gzip/] != nil)
239
- end
240
-
241
- def inspect
242
- str = "HTTP/%s %s (%s)" % [@http_version, @status, @message]
243
- str << $/ + headers.join($/)
244
- str << $/ + $/ + body if body
245
- str
246
- end
247
-
248
- def to_s
249
- str = "%s: HTTP/%s %s (%s)" % [time.strftime(NICE_TIME_FORMAT), @http_version, @status, @message]
250
- str
251
- end
252
-
253
-
254
- def cookies
255
- return [] unless header.is_a?(Array) && !header[:Set_Cookie].empty?
256
- header[:Set_Cookie]
257
- end
258
- end
259
- end
4
+ Stella::Utils.require_glob(STELLA_LIB_HOME, 'stella', 'data', 'http', '*.rb')
@@ -0,0 +1,85 @@
1
+
2
+ module Stella::Data
3
+
4
+ module Helpers
5
+
6
+
7
+ def random(input=nil)
8
+
9
+ Proc.new do
10
+ value = case input.class.to_s
11
+ when "Symbol"
12
+ resource(input)
13
+ when "Array"
14
+ input
15
+ when "Range"
16
+ input.to_a
17
+ when "Fixnum"
18
+ Stella::Utils.strand( input )
19
+ when "NilClass"
20
+ Stella::Utils.strand( rand(100) )
21
+ end
22
+ Stella.ld "RANDVALUES: #{input} #{value.inspect}"
23
+ value = value[ rand(value.size) ] if value.is_a?(Array)
24
+ Stella.ld "SELECTED: #{value}"
25
+ value
26
+ end
27
+ end
28
+
29
+ def sequential(input=nil)
30
+ digest = input.gibbler
31
+ Proc.new do
32
+ value = case input.class.to_s
33
+ when "Symbol"
34
+ ret = resource(input)
35
+ ret
36
+ when "Array"
37
+ input
38
+ when "Range"
39
+ input.to_a
40
+ end
41
+ Stella.ld "SEQVALUES: #{input} #{value.inspect}"
42
+ @sequential_offset ||= {}
43
+ @sequential_offset[digest] ||= 0
44
+ if value.is_a?(Array)
45
+ size = value[ @sequential_offset[digest] ].size
46
+ value = value[ @sequential_offset[digest] ]
47
+ @sequential_offset[digest] += 1
48
+ @sequential_offset[digest] = 0 if @sequential_offset[digest] > size
49
+ end
50
+ Stella.ld "SELECTED: #{value}"
51
+ value
52
+ end
53
+ end
54
+
55
+ def rsequential(input=nil)
56
+ digest = input.gibbler
57
+ Proc.new do
58
+ value = case input.class.to_s
59
+ when "Symbol"
60
+ ret = resource(input)
61
+ ret
62
+ when "Array"
63
+ input
64
+ when "Range"
65
+ input.to_a
66
+ end
67
+ Stella.ld "RSEQVALUES: #{input} #{value.inspect}"
68
+ @rsequential_offset ||= {}
69
+ @rsequential_offset[digest] ||= value.size-1 rescue 1
70
+ if value.is_a?(Array)
71
+ size = value[ @rsequential_offset[digest] ].size
72
+ value = value[ @rsequential_offset[digest] ]
73
+ @rsequential_offset[digest] -= 1
74
+ @rsequential_offset[digest] = size if @rsequential_offset[digest] < 0
75
+ end
76
+ Stella.ld "SELECTED: #{value}"
77
+ value
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+
85
+ Stella::Utils.require_glob(STELLA_LIB_HOME, 'stella', 'data', '*.rb')
data/lib/stella/dsl.rb ADDED
@@ -0,0 +1,5 @@
1
+
2
+
3
+ module Stella::DSL; end
4
+
5
+ Stella::Utils.require_glob(STELLA_LIB_HOME, 'stella', 'dsl', '*.rb')
@@ -0,0 +1,39 @@
1
+
2
+ module Stella::Engine
3
+ module Functional
4
+ extend Stella::Engine::Base
5
+ extend self
6
+
7
+ def run(plan, opts={})
8
+ opts = {
9
+ :hosts => [],
10
+ :benchmark => false,
11
+ :repetitions => 1
12
+ }.merge! opts
13
+ Stella.ld "OPTIONS: #{opts.inspect}"
14
+ Stella.li2 "Hosts: " << opts[:hosts].join(', ') if !opts[:hosts].empty?
15
+
16
+ client = Stella::Client.new opts[:hosts].first
17
+ client.add_observer(self)
18
+ client.enable_benchmark_mode if opts[:benchmark]
19
+
20
+ plan.usecases.each_with_index do |uc,i|
21
+ desc = (uc.desc || "Usecase ##{i+1}")
22
+ Stella.li ' %-65s '.att(:reverse).bright % [desc]
23
+ Stella.rescue { client.execute uc }
24
+ end
25
+
26
+ Drydock::Screen.flush
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+ __END__
33
+
34
+
35
+ $ stella verify -p examples/basic/plan.rb http://localhost:3114
36
+ $ stella load -p examples/basic/plan.rb http://localhost:3114
37
+ $ stella remote-load -p examples/basic/plan.rb http://localhost:3114
38
+ $ stella remote-verify -p examples/basic/plan.rb http://localhost:3114
39
+
@@ -0,0 +1,106 @@
1
+
2
+ module Stella::Engine
3
+ module Load
4
+ extend Stella::Engine::Base
5
+ extend self
6
+
7
+ def run(plan, opts={})
8
+ opts = {
9
+ :hosts => [],
10
+ :users => 1,
11
+ :time => nil,
12
+ :benchmark => false,
13
+ :repetitions => 1
14
+ }.merge! opts
15
+ opts[:users] = plan.usecases.size if opts[:users] < plan.usecases.size
16
+ opts[:users] = 1000 if opts[:users] > 1000
17
+
18
+ Stella.ld "OPTIONS: #{opts.inspect}"
19
+ Stella.li2 "Hosts: " << opts[:hosts].join(', ')
20
+
21
+ packages = build_thread_package plan, opts
22
+ Drydock::Screen.flush
23
+
24
+ Thread.ify packages, :threads => opts[:users] do |package|
25
+ (1..opts[:repetitions]).to_a.each do |rep|
26
+ # We store client specific data in the usecase
27
+ # so we clone it here so each thread is unique.
28
+ Stella.rescue { package.client.execute package.usecase }
29
+ Drydock::Screen.flush
30
+ end
31
+ end
32
+
33
+ Drydock::Screen.flush
34
+ end
35
+
36
+ protected
37
+ class ThreadPackage
38
+ attr_accessor :index
39
+ attr_accessor :client
40
+ attr_accessor :usecase
41
+ def initialize(i, c, u)
42
+ @index, @client, @usecase = i, c, u
43
+ end
44
+ end
45
+
46
+ def build_thread_package(plan, opts)
47
+ packages, pointer = Array.new(opts[:users]), 0
48
+ plan.usecases.each_with_index do |usecase,i|
49
+
50
+ count = case opts[:users]
51
+ when 0..9
52
+ if (opts[:users] % plan.usecases.size > 0)
53
+ raise Stella::Testplan::WackyRatio, "User count does not match usecase count evenly"
54
+ else
55
+ (opts[:users] / plan.usecases.size)
56
+ end
57
+ else
58
+ (opts[:users] * usecase.ratio).to_i
59
+ end
60
+
61
+ Stella.ld "THREAD PACKAGE: #{usecase.desc} #{pointer} #{(pointer+count)} #{count}"
62
+ # Fill the thread_package with the contents of the block
63
+ packages.fill(pointer, count) do |index|
64
+ Stella.li2 "Creating client ##{index+1} "
65
+ client = Stella::Client.new opts[:hosts].first, index+1
66
+ client.add_observer(self)
67
+ client.enable_benchmark_mode if opts[:benchmark]
68
+ ThreadPackage.new(index+1, client, usecase.clone)
69
+ end
70
+ pointer += count
71
+ end
72
+ packages
73
+ end
74
+
75
+ def update_send_request(client_id, usecase, meth, uri, req, params, counter)
76
+
77
+ end
78
+
79
+ def update_receive_response(client_id, usecase, meth, uri, req, params, container)
80
+ desc = "#{usecase.desc} > #{req.desc}"
81
+ Stella.li ' Client%-3s %3d %-6s %-45s %s' % [client_id, container.status, req.http_method, desc, uri]
82
+ end
83
+
84
+ def update_execute_response_handler(client_id, req, container)
85
+ end
86
+
87
+ def update_error_execute_response_handler(client_id, ex, req, container)
88
+ end
89
+
90
+ def update_request_error(client_id, usecase, meth, uri, req, params, ex)
91
+ desc = "#{usecase.desc} > #{req.desc}"
92
+ Stella.le ' Client%-3s %-45s %s' % [client_id, desc, ex.message]
93
+ end
94
+
95
+
96
+ end
97
+ end
98
+
99
+ __END__
100
+
101
+
102
+ $ stella verify -p examples/basic/plan.rb http://localhost:3114
103
+ $ stella load -p examples/basic/plan.rb http://localhost:3114
104
+ $ stella remote-load -p examples/basic/plan.rb http://localhost:3114
105
+ $ stella remote-verify -p examples/basic/plan.rb http://localhost:3114
106
+
@@ -0,0 +1,55 @@
1
+
2
+
3
+ module Stella::Engine
4
+ module Base
5
+ extend self
6
+ def run
7
+ raise "override the run method"
8
+ end
9
+
10
+ def update(*args)
11
+ what, *args = args
12
+ Stella.ld "OBSERVER UPDATE: #{what}"
13
+ if !respond_to?("update_#{what}")
14
+ Stella.ld "NO UPDATE HANDLER FOR: #{what}"
15
+ else
16
+ Stella.rescue {
17
+ self.send("update_#{what}", *args)
18
+ }
19
+ end
20
+ end
21
+
22
+ def update_send_request(client_id, usecase, meth, uri, req, params, counter)
23
+ notice = "repeat: #{counter-1}" if counter > 1
24
+ Stella.li2 ' ' << " %-46s %16s ".att(:reverse) % [req.desc, notice]
25
+ end
26
+
27
+ def update_receive_response(client_id, usecase, meth, uri, req, params, container)
28
+ Stella.li ' %-59s %3d' % [uri, container.status]
29
+ Stella.li2 " Method: " << req.http_method
30
+ Stella.li2 " Params: " << params.inspect
31
+ Stella.li3 $/, " Headers:"
32
+ container.headers.all.each do |pair|
33
+ Stella.li3 " %s: %s" % pair
34
+ end
35
+ Stella.li3 $/, " Content:"
36
+ Stella.li3 container.body.empty? ? ' [empty]' : container.body
37
+ Stella.li2 $/
38
+ end
39
+
40
+ def update_execute_response_handler(client_id, req, container)
41
+ end
42
+
43
+ def update_error_execute_response_handler(client_id, ex, req, container)
44
+ Stella.le ex.message
45
+ end
46
+
47
+ def update_request_error(client_id, usecase, meth, uri, req, params, ex)
48
+ desc = "#{usecase.desc} > #{req.desc}"
49
+ Stella.le ' Client%-3s %-45s %s' % [client_id, desc, ex.message]
50
+ end
51
+
52
+ end
53
+ end
54
+
55
+ Stella::Utils.require_glob(STELLA_LIB_HOME, 'stella', 'engine', '*.rb')
@@ -0,0 +1,15 @@
1
+
2
+
3
+ module Stella
4
+ class Error < RuntimeError
5
+ def initialize(obj=nil); @obj = obj; end
6
+ def message; "#{self.class}: #{@obj}"; end
7
+ end
8
+
9
+ class InvalidOption < Stella::Error
10
+ end
11
+
12
+ class NoHostDefined < Stella::Error
13
+ end
14
+
15
+ end
@@ -0,0 +1,18 @@
1
+
2
+
3
+ module Stella
4
+ module Guidelines
5
+ extend self
6
+ AFE = "Always fail early"
7
+ ABA = "Always be accurate"
8
+ CBC = "Consistency before cuteness"
9
+ NDP = "No defensive programming"
10
+ def inspect
11
+ all = Stella::Guidelines.constants
12
+ g = all.collect { |c| '%s="%s"' % [c, const_get(c)] }
13
+ %q{#<Stella::Guidelines:0x%s %s>} % [self.object_id, g.join(' ')]
14
+ end
15
+ end
16
+ end
17
+
18
+ p Stella::Guidelines if __FILE__ == $0
@@ -0,0 +1,2 @@
1
+
2
+ Stella::Utils.require_glob(STELLA_LIB_HOME, 'stella', 'mixins', '*.rb')
data/lib/stella/stats.rb CHANGED
@@ -1,5 +1,4 @@
1
1
 
2
-
3
2
  module Stella
4
3
  # Based on Mongrel::Stats, Copyright (c) 2005 Zed A. Shaw
5
4
  class Stats
@@ -17,12 +16,9 @@ class Stats
17
16
 
18
17
  # Resets the internal counters so you can start sampling again.
19
18
  def reset
20
- @sum = 0.0
21
- @sumsq = 0.0
19
+ @n, @sum, @sumsq = 0.0, 0.0, 0.0
22
20
  @last_time = Time.new
23
- @n = 0.0
24
- @min = 0.0
25
- @max = 0.0
21
+ @min, @max = 0.0, 0.0
26
22
  end
27
23
 
28
24
  # Adds a sampling to the calculations.
@@ -81,4 +77,4 @@ class Stats
81
77
  @last_time = now
82
78
  end
83
79
  end
84
- end
80
+ end
@@ -0,0 +1,26 @@
1
+
2
+ module Stella
3
+ class Testplan
4
+
5
+ class Stats
6
+ include Gibbler::Complex
7
+ attr_reader :requests
8
+
9
+ def initialize
10
+ @requests = OpenStruct.new
11
+ reset
12
+ end
13
+
14
+ def total_requests
15
+ @requests.successful + @requests.failed
16
+ end
17
+
18
+ def reset
19
+ @requests.successful = 0
20
+ @requests.failed = 0
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end