stella 0.8.8.001 → 2.0.1.001

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/CHANGES.txt +9 -1
  2. data/Gemfile +19 -0
  3. data/Gemfile.lock +50 -0
  4. data/README.md +5 -79
  5. data/Rakefile +10 -7
  6. data/Rudyfile +1 -1
  7. data/TODO +31 -0
  8. data/VERSION.yml +3 -4
  9. data/bin/stella +23 -81
  10. data/certs/README.txt +17 -0
  11. data/certs/cacerts.pem +1529 -0
  12. data/certs/gd-class2-root.crt +24 -0
  13. data/certs/gd_bundle.crt +76 -0
  14. data/certs/gd_intermediate.crt +29 -0
  15. data/certs/startssl-ca.pem +44 -0
  16. data/certs/startssl-sub.class1.server.ca.pem +36 -0
  17. data/certs/stella-master.crt +1738 -0
  18. data/lib/stella.rb +191 -123
  19. data/lib/stella/cli.rb +47 -67
  20. data/lib/stella/client.rb +424 -360
  21. data/lib/stella/core_ext.rb +527 -0
  22. data/lib/stella/engine.rb +126 -419
  23. data/lib/stella/report.rb +391 -0
  24. data/lib/stella/testplan.rb +432 -306
  25. data/lib/stella/utils.rb +227 -2
  26. data/stella.gemspec +56 -55
  27. data/try/00_basics_try.rb +29 -0
  28. data/try/01_selectable_try.rb +25 -0
  29. data/try/09_utils_try.rb +67 -0
  30. data/try/10_stella_object_try.rb +49 -0
  31. data/try/40_report_try.rb +133 -0
  32. data/try/90_class_syntax_try.rb +13 -0
  33. data/try/emhttp.rb +62 -0
  34. data/try/rubyroute.rb +70 -0
  35. data/try/support/file.bmp +0 -0
  36. data/try/support/file.gif +0 -0
  37. data/try/support/file.ico +0 -0
  38. data/try/support/file.jpeg +0 -0
  39. data/try/support/file.jpg +0 -0
  40. data/try/support/file.png +0 -0
  41. data/try/traceviz.rb +60 -0
  42. data/vendor/httpclient-2.1.5.2/httpclient/session.rb +5 -2
  43. metadata +81 -53
  44. data/examples/cookies/plan.rb +0 -49
  45. data/examples/csvdata/plan.rb +0 -32
  46. data/examples/csvdata/search_terms.csv +0 -14
  47. data/examples/dynamic/plan.rb +0 -60
  48. data/examples/essentials/logo.png +0 -0
  49. data/examples/essentials/plan.rb +0 -248
  50. data/examples/essentials/search_terms.txt +0 -19
  51. data/examples/exceptions/plan.rb +0 -20
  52. data/examples/httpauth/plan.rb +0 -33
  53. data/examples/timeout/plan.rb +0 -18
  54. data/examples/variables/plan.rb +0 -41
  55. data/lib/stella/client/container.rb +0 -378
  56. data/lib/stella/common.rb +0 -363
  57. data/lib/stella/data.rb +0 -59
  58. data/lib/stella/data/http.rb +0 -189
  59. data/lib/stella/engine/functional.rb +0 -156
  60. data/lib/stella/engine/load.rb +0 -516
  61. data/lib/stella/guidelines.rb +0 -18
  62. data/lib/stella/logger.rb +0 -150
  63. data/lib/stella/utils/httputil.rb +0 -266
  64. data/try/01_numeric_mixins_tryouts.rb +0 -40
  65. data/try/12_digest_tryouts.rb +0 -42
  66. data/try/70_module_usage.rb +0 -21
  67. data/try/api/10_functional.rb +0 -20
  68. data/try/configs/failed_requests.rb +0 -31
  69. data/try/configs/global_sequential.rb +0 -18
  70. data/try/proofs/thread_queue.rb +0 -21
data/lib/stella/engine.rb CHANGED
@@ -1,438 +1,145 @@
1
1
 
2
- module Stella::Engine
3
- @service = nil
4
- class << self
5
- attr_accessor :service
6
- end
7
- # See functional.rb
8
- class Log < Storable
9
- include Selectable::Object
10
- field :stamp
11
- field :uniqueid
12
- field :clientid
13
- field :planid
14
- field :caseid
15
- field :reqid
16
- field :httpmethod
17
- field :httpstatus
18
- field :uri
19
- field :params
20
- field :headers
21
- field :response_headers
22
- field :response_body
23
- end
24
- class Base
25
-
2
+ class Stella
3
+ module Engine
4
+ @modes = {}
26
5
  class << self
27
- attr_accessor :timers, :counts
28
- end
29
-
30
- attr_reader :testrun, :logdir, :opts
31
-
32
- def initialize(opts={})
33
- @opts = opts
34
- @logdir = nil
35
- end
36
-
37
- def update(*args)
38
- what, *args = args
39
- if respond_to?("update_#{what}")
40
- #Stella.ld "OBSERVER UPDATE: #{what}"
41
- Stella.rescue { self.send("update_#{what}", *args) }
42
- else
43
- Stella.ld "NO UPDATE HANDLER FOR: #{what}"
6
+ attr_reader :modes
7
+ def mode?(name)
8
+ @modes.has_key? name
44
9
  end
45
- end
46
-
47
- def runid(plan)
48
- args = [Stella.sysinfo.hostname, Stella.sysinfo.user]
49
- args.push Stella::START_TIME, plan
50
- args.id
51
- end
52
-
53
-
54
- def run; raise; end
55
- def update_usecase_quit(client_id, msg) raise end
56
- def update_request_repeat(client_id, counter, total) raise end
57
- def update_stats(client_id, http_client, usecase, req) raise end
58
- def update_prepare_request(*args) raise end
59
- def update_send_request(*args) raise end
60
- def update_receive_response(*args) raise end
61
- def update_execute_response_handler(*args) raise end
62
- def update_error_execute_response_handler(*args) raise end
63
- def update_request_error(*args) raise end
64
- def request_unhandled_exception(*args) raise end
65
- def update_request_fail(*args) raise end
66
-
67
- end
68
-
69
- autoload :Functional, 'stella/engine/functional'
70
- autoload :Load, 'stella/engine/load'
71
-
72
- # These timers are interesting from a reporting perspective.
73
- Benelux.add_counter Stella::Client, :execute_response_handler
74
- Benelux.add_timer HTTPClient, :do_request, :response_time
75
- ## These are contained in connect
76
- #Benelux.add_timer HTTPClient::Session, :create_socket
77
- #Benelux.add_timer HTTPClient::Session, :create_ssl_socket
78
- Benelux.add_timer HTTPClient::Session, :connect, :socket_connect
79
- Benelux.add_timer HTTPClient::Session, :query, :send_request
80
- Benelux.add_timer HTTPClient::Session, :socket_gets_first_byte, :first_byte
81
- Benelux.add_timer HTTPClient::Session, :get_body, :receive_response
82
-
83
- end
84
-
85
- class Stella::Testrun < Storable
86
- CLIENT_LIMIT = 1000
87
- GRANULARITY = 5
88
- include Gibbler::Complex
89
- field :id => String, &gibbler_id_processor
90
- field :status => String
91
- field :userid => String
92
- field :start_time => Integer
93
- field :end_time => Integer
94
- field :clients => Integer
95
- field :duration => Integer
96
- field :arrival => Float
97
- field :repetitions => Integer
98
- field :timeout => Integer
99
- field :wait => Range
100
- field :nowait => TrueClass
101
- field :logsize => Integer
102
- field :granularity => Integer
103
- field :withparam => TrueClass
104
- field :withheader => TrueClass
105
- field :notemplates => TrueClass
106
- field :nostats => TrueClass
107
- field :stats => Hash
108
- field :samples => Array
109
- field :event_probes => Array
110
- field :agent => String
111
- field :mode # verify or generate
112
- field :plan
113
- field :hosts
114
- field :log
115
- gibbler :plan, :hosts, :mode, :clients, :duration, :repetitions, :wait, :start_time, :userid
116
- def initialize(plan=nil, opts={})
117
- @plan = plan
118
- @granularity = GRANULARITY
119
- @logsize = 1
120
- @log = []
121
- process_options! opts if !plan.nil? && !opts.empty?
122
- reset_stats
123
- end
124
- def id
125
- Gibbler::Digest.new(@id || self.digest)
126
- end
127
- def running?
128
- (self.status.to_s == "running")
129
- end
130
- def done?
131
- (self.status.to_s == "done")
132
- end
133
- def new?
134
- (self.status.to_s == "new")
135
- end
136
- def pending?
137
- (self.status.to_s == "pending")
138
- end
139
- def has_log?() !@log.nil? && !@log.empty? end
140
- def elapsed_seconds
141
- return 0 if @start_time.nil? || @start_time <= 0
142
- return Time.now.utc.to_i - @start_time if @end_time.nil? || @end_time <= 0
143
- @end_time - @start_time
144
- end
145
-
146
- def client_options
147
- opts = {
148
- :nowait => self.nowait || false,
149
- :notemplates => self.notemplates || false,
150
- :withparam => self.withparam || false,
151
- :withheader => self.withheader || false,
152
- :wait => self.wait || 0,
153
- :timeout => self.timeout
154
- }
155
- end
156
-
157
- def process_options!(opts={})
158
-
159
- unless opts.empty?
160
- opts = {
161
- :hosts => [],
162
- :clients => 1,
163
- :duration => 0,
164
- :wait => 0,
165
- :nowait => false,
166
- :arrival => nil,
167
- :repetitions => 1,
168
- :mode => :verify
169
- }.merge! opts
170
-
171
- opts.each_pair do |n,v|
172
- self.send("#{n}=", v) if has_field? n
10
+ def load(name)
11
+ @modes[name]
173
12
  end
174
-
175
- Stella.ld " Options: #{opts.inspect}"
176
- end
177
-
178
- @id &&= Gibbler::Digest.new(@id)
179
-
180
- @samples ||= []
181
- @stats ||= { :summary => {} }
182
-
183
- @status ||= "new"
184
- if @event_probes.nil? || @event_probes.empty?
185
- @event_probes = ['response_time', 'response_content_size']
186
- end
187
-
188
- @start_time ||= Time.now.to_i
189
- @end_time ||= 0
190
-
191
- @event_probes.collect! { |event| event.to_s }
192
-
193
- @duration ||= 0
194
- @repetitions ||= 0
195
-
196
- # Support durations in the form "30m", "2h", etc...
197
- @duration = @duration.in_seconds if String === @duration
198
-
199
- @timeout &&= @timeout.to_f
200
- @clients &&= @clients.to_i
201
- @duration &&= @duration.to_i
202
- @arrival &&= @arrival.to_f
203
- @repetitions &&= @repetitions.to_i
204
-
205
- @mode &&= @mode.to_sym
206
-
207
- @hosts = [@hosts] unless Array === @hosts
208
-
209
- @hosts.collect! do |host|
210
- host.to_s
211
- end
212
-
213
- if @clients > CLIENT_LIMIT
214
- Stella.stdout.info2 "Client limit is #{CLIENT_LIMIT}"
215
- @clients = CLIENT_LIMIT
216
- end
217
-
218
- # Parses 60m -> 3600. See mixins.
219
- @duration = @duration.in_seconds
220
-
221
- raise Stella::WackyDuration, @duration if @duration.nil?
222
-
223
- @mode &&= @mode.to_sym
224
-
225
- unless [:verify, :generate].member?(@mode)
226
- raise Stella::Error, "Unsupported mode: #{@mode}"
227
- end
228
-
229
- if Hash === self.plan # When reconstituting from JSON
230
- self.plan = Stella::Testplan.from_hash(self.plan)
231
- end
232
-
233
- if Stella::Testplan === self.plan
234
- # Plan must be frozen before running (see freeze methods)
235
- self.plan.frozen? || self.plan.freeze
236
- @clients = plan.usecases.size if @clients < plan.usecases.size
237
- end
238
-
239
- @id ||= self.gibbler # populate id
240
-
241
- end
242
-
243
- def freeze
244
- return if frozen?
245
- Stella.ld "FREEZE TESTRUN: #{self.id}"
246
- plan.freeze
247
- super
248
- self
249
- end
250
-
251
- def log_dir
252
- # Don't use @start_time here b/c that won't be set
253
- # until just before the actual testing starts.
254
- stamp = Stella::START_TIME.strftime("%Y%m%d-%H-%M-%S")
255
- stamp << "-#{self.plan.id.shorter}" unless self.plan.nil?
256
- l = File.join Stella::Config.project_dir, 'log', stamp
257
- FileUtils.mkdir_p l unless File.exists?(l) || Stella::Logger.disabled?
258
- l
259
- end
260
-
261
- def log_path(file)
262
- File.join log_dir, file
263
- end
264
-
265
-
266
- def run(opts={})
267
- Stella.agent = @agent unless @agent.nil? || @agent.empty?
268
- engine = case self.mode
269
- when :verify
270
- Stella::Engine::Functional.new(opts)
271
- when :generate
272
- Stella::Engine::Load.new(opts)
273
- else
274
- raise Stella::Error, "Unsupported mode: #{self.mode}"
275
- end
276
- engine.run self
277
- @status = "done"
278
- #self.freeze
279
- self
280
- end
281
-
282
- def save
283
- unless Stella::Logger.disabled?
284
- path = log_path('stats')
285
- Stella::Utils.write_to_file(path, self.to_json, 'w', 0644)
286
- end
287
- end
288
-
289
- def reset_stats
290
- @samples = []
291
- @stats = { 'summary' => {} }
292
- unless @plan.nil?
293
- @plan.usecases.each do |uc|
294
- @event_probes.each do |event|
295
- event &&= event.to_s
296
- @stats['summary'][event] = Benelux::Stats::Calculator.new
297
- @stats[uc.id] ||= { 'summary' => {} }
298
- @stats[uc.id]['summary'][event] = Benelux::Stats::Calculator.new
299
- @stats[uc.id]['summary']['status'] ||= {}
300
- @stats['summary']['status'] ||= {}
301
- uc.requests.each do |req|
302
- @stats[uc.id][req.id] ||= {}
303
- @stats[uc.id][req.id][event] = Benelux::Stats::Calculator.new
304
- @stats[uc.id][req.id]['status'] ||= {}
305
- end
13
+ def run testrun, opts={}
14
+ case testrun.mode.to_sym
15
+ when :checkup
16
+ Stella::Engine::Checkup.run testrun, opts
17
+ else
18
+ Stella.le "Unknown Engine: #{testrun.mode}"
306
19
  end
307
20
  end
308
21
  end
309
- end
310
-
311
-
312
- def self.from_hash(hash={})
313
- me = super(hash)
314
- me.plan = Stella::Testplan.from_hash(me.plan)
315
- me.process_options! unless me.plan.nil?
316
-
317
- me.samples.collect! do |sample|
318
- Stella::Testrun::Sample.from_hash(sample)
319
- end
320
-
321
- me.plan.usecases.uniq.each_with_index do |uc,i|
322
- uc.requests.each do |req|
323
- me.event_probes.each_with_index do |event,idx| # do_request, etc...
324
- event &&= event.to_s
325
- next unless me.stats[uc.id][req.id].has_key?(event)
326
- next if Benelux::Stats::Calculator === me.stats[uc.id][req.id][event]
327
- me.stats[uc.id][req.id][event] =
328
- Benelux::Stats::Calculator.from_hash(me.stats[uc.id][req.id][event])
329
- me.stats[uc.id]['summary'][event] =
330
- Benelux::Stats::Calculator.from_hash(me.stats[uc.id]['summary'][event])
331
- me.stats['summary'][event] =
332
- Benelux::Stats::Calculator.from_hash(me.stats['summary'][event])
22
+ module Base
23
+ def self.included(obj)
24
+ obj.extend ClassMethods
25
+ end
26
+ module ClassMethods
27
+ attr_reader :mode
28
+ def register(mode)
29
+ @mode = mode
30
+ Stella::Engine.modes[mode] = self
333
31
  end
334
32
  end
335
33
  end
336
-
337
- me
338
- end
339
-
340
- def has_errors?
341
- return false if @samples.nil? || @samples.empty?
342
- @samples.each { |sam|
343
- return true if sam.has_errors?
344
- }
345
- false
346
34
  end
347
-
348
- def add_sample batch, concurrency, tl
349
-
350
- opts = {
351
- :batch => batch,
352
- :duration => tl.duration,
353
- :stamp => Time.now.utc.to_i,
354
- :concurrency => concurrency
355
- }
356
-
357
- sam = Stella::Testrun::Sample.new opts
358
-
359
- @plan.usecases.uniq.each_with_index do |uc,i|
360
- sam.stats[uc.id] ||= { }
361
- uc.requests.each do |req|
362
- sam.stats[uc.id][req.id] ||= {}
363
- filter = [uc.id, req.id]
364
- @event_probes.each_with_index do |event,idx| # do_request, etc...
365
- event &&= event.to_s
366
- stats = tl.stats.group(event.to_sym)[filter]
367
- unless sam.stats[uc.id][req.id].has_key?(event)
368
- stats[uc.id][req.id][event] ||= {}
369
- @stats[uc.id][req.id][event] ||= Benelux::Stats::Calculator.new
370
- @stats[uc.id]['summary'][event] ||= Benelux::Stats::Calculator.new
371
- @stats['summary'][event] ||= Benelux::Stats::Calculator.new
35
+
36
+ module Engine
37
+ module Checkup
38
+ include Engine::Base
39
+ extend self
40
+ def run testrun, opts={}
41
+ opts = parse_opts testrun.options, opts
42
+ Stella.ld "testrun opts: #{opts.inspect}"
43
+ threads = []
44
+ testrun.stime = Stella.now
45
+ testrun.running!
46
+ opts[:concurrency].times do
47
+ threads << Thread.new do
48
+ client = Stella::Client.new opts
49
+ Benelux.current_track "client_#{client.clientid.shorten}"
50
+ begin
51
+ opts[:repetitions].times do |idx|
52
+ Stella.li '%-61s %s' % [testrun.plan.desc, testrun.plan.planid.shorten(12)] if Stella.noise >= 2
53
+ testrun.plan.usecases.each_with_index do |uc,i|
54
+ if opts[:usecases].nil? || opts[:usecases].member?(uc.class)
55
+ Benelux.current_track.add_tags :usecase => uc.id
56
+ Stella.rescue {
57
+ Stella.li ' %-60s %s' % [uc.desc, uc.ucid.shorten(12)] if Stella.noise >= 2
58
+ client.execute uc do |session|
59
+ Stella.li ' %3d %4s %-76s' % [session.status, session.http_method.upcase, session.uri] if Stella.noise >= 2
60
+ if Stella.noise >= 3
61
+ Stella.li ' %s' % [session.req.header.dump.split(/\n/).join("\n ")]
62
+ Stella.li
63
+ Stella.li ' %s' % [session.res.header.dump.split(/\n/).join("\n ")]
64
+ end
65
+ end
66
+ }
67
+ if client.exception
68
+ Stella.li ' %4s %s (%s)' % ['', client.exception.message, client.exception.class]
69
+ # TODO: use a throw. This won't stop the next repetition.
70
+ break if Stella::TestplanQuit === client.exception
71
+ end
72
+ else
73
+ Stella.li ' %-60s %s' % ["#{uc.desc} (skipped)", uc.ucid.shorten(12)] if Stella.noise >= 2
74
+ end
75
+ Benelux.current_track.remove_tags :usecase
76
+ end
77
+ end
78
+ rescue Interrupt
79
+ Stella.li "Skipping..."
80
+ testrun.etime = Stella.now
81
+ testrun.fubar!
82
+ exit 1
83
+ rescue => ex
84
+ Stella.li ex.message
85
+ Stella.li ex.backtrace if Stella.debug?
86
+ end
372
87
  end
373
- # When we don't merge the stats from benelux,
374
- # the each calculator contains just one sample.
375
- # So when we grab the sum, it's just one event sample.
376
- sam.stats[uc.id][req.id][event] = stats.collect {|c| c.sum }
377
- # Tally request, usecase and total summaries at the same time.
378
- @stats[uc.id][req.id][event] += stats.merge
379
- @stats[uc.id]['summary'][event] += stats.merge
380
- @stats['summary'][event] += stats.merge
381
88
  end
382
- resp = tl.stats.group(:execute_response_handler)[filter]
383
- resp.tag_values(:status).each do |status|
384
- sam.stats[uc.id][req.id]['status'] ||= {}
385
- count = resp[:status => status].size
386
- sam.stats[uc.id][req.id]['status'][status.to_i] = count
89
+
90
+ begin
91
+ threads.each { |thread| thread.join }
92
+ timeline = Benelux.merge_tracks
93
+ rescue Interrupt
94
+ Stella.li "Skipping..."
95
+ testrun.etime = Stella.now
96
+ testrun.fubar!
97
+ exit 1
387
98
  end
388
- tmp = tl.messages.filter([filter, :exception].flatten)
389
- unless tmp.empty?
390
- sam.errors[uc.id] ||= {}
391
- sam.errors[uc.id][req.id] ||= []
392
- sam.errors[uc.id][req.id].push *tmp
99
+
100
+ begin
101
+ testrun.etime = Stella.now
102
+ testrun.report = Stella::Report.new timeline, testrun.runid
103
+ testrun.report.process
104
+ testrun.report.fubars? ? testrun.fubar! : testrun.done!
105
+ rescue Interrupt
106
+ Stella.li "Exiting..."
107
+ testrun.etime = Stella.now
108
+ testrun.fubar!
109
+ exit 1
110
+ rescue => ex
111
+ Stella.li ex.message
112
+ Stella.li ex.backtrace if Stella.debug?
113
+ testrun.etime = Stella.now
114
+ testrun.fubar!
393
115
  end
116
+ Benelux.reset # If we run again, the old stats still remain
117
+ testrun
394
118
  end
395
- end
396
-
397
- @samples << sam
398
-
399
- sam
400
- end
401
-
402
-
403
- class Sample < Storable
404
- field :batch
405
- field :concurrency
406
- field :stamp
407
- field :duration
408
- field :stats => Hash
409
- field :errors => Hash
410
- #gibbler :batch, :concurrency, :stamp, :duration
411
- def initialize(opts={})
412
- opts.each_pair do |n,v|
413
- self.send("#{n}=", v) if has_field? n
119
+
120
+ private
121
+ def parse_opts(runopts, opts)
122
+ runopts.keys.each do |key|
123
+ runopts[key.to_sym] = runopts.delete(key) if String === key
124
+ end
125
+ opts.keys.each do |key|
126
+ opts[key.to_sym] = opts.delete(key) if String === key
127
+ end
128
+ runopts[:repetitions] ||= 1
129
+ runopts[:concurrency] ||= 1
130
+ runopts[:wait] ||= 1
131
+ runopts.merge opts
414
132
  end
415
- @stats, @errors = {}, {}
416
- end
417
- def has_errors?
418
- !@errors.nil? && !@errors.empty?
419
- end
420
- def self.from_hash(hash={})
421
- me = super(hash)
422
- #stats = {}
423
- me.stats.each_pair { |ucid,uchash|
424
- uchash.each_pair { |reqid,reqhash|
425
- if me.stats[ucid][reqid].has_key? 'status'
426
- me.stats[ucid][reqid]['status'].keys do |status|
427
- value = me.stats[ucid][reqid]['status'][status]
428
- me.stats[ucid][reqid]['status'].delete status
429
- me.stats[ucid][reqid]['status'][status.to_i] = value.to_i
430
- end
431
- end
432
- }
433
- }
434
- #me.stats = stats
435
- me
133
+
134
+ Benelux.add_timer HTTPClient, :do_request, :response_time
135
+ Benelux.add_timer HTTPClient::Session, :connect, :socket_connect
136
+ Benelux.add_timer HTTPClient::Session, :send_request, :send_request
137
+ Benelux.add_timer HTTPClient::Session, :socket_gets_first_byte, :first_byte
138
+ Benelux.add_timer HTTPClient::Session, :get_body, :last_byte
139
+
140
+ register :checkup
436
141
  end
142
+
437
143
  end
438
- end
144
+
145
+ end