stella 0.6.0 → 0.7.0.002

Sign up to get free protection for your applications and to get access to all the features.
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