solutious-stella 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/CHANGES.txt +36 -0
  2. data/README.textile +162 -0
  3. data/Rakefile +88 -0
  4. data/bin/stella +12 -0
  5. data/bin/stella.bat +12 -0
  6. data/lib/daemonize.rb +56 -0
  7. data/lib/pcaplet.rb +180 -0
  8. data/lib/stella.rb +101 -0
  9. data/lib/stella/adapter/ab.rb +337 -0
  10. data/lib/stella/adapter/base.rb +106 -0
  11. data/lib/stella/adapter/httperf.rb +305 -0
  12. data/lib/stella/adapter/pcap_watcher.rb +221 -0
  13. data/lib/stella/adapter/proxy_watcher.rb +76 -0
  14. data/lib/stella/adapter/siege.rb +341 -0
  15. data/lib/stella/cli.rb +258 -0
  16. data/lib/stella/cli/agents.rb +73 -0
  17. data/lib/stella/cli/base.rb +55 -0
  18. data/lib/stella/cli/language.rb +18 -0
  19. data/lib/stella/cli/localtest.rb +78 -0
  20. data/lib/stella/cli/sysinfo.rb +16 -0
  21. data/lib/stella/cli/watch.rb +278 -0
  22. data/lib/stella/command/base.rb +40 -0
  23. data/lib/stella/command/localtest.rb +358 -0
  24. data/lib/stella/data/domain.rb +82 -0
  25. data/lib/stella/data/http.rb +131 -0
  26. data/lib/stella/logger.rb +84 -0
  27. data/lib/stella/response.rb +85 -0
  28. data/lib/stella/storable.rb +201 -0
  29. data/lib/stella/support.rb +276 -0
  30. data/lib/stella/sysinfo.rb +257 -0
  31. data/lib/stella/test/definition.rb +79 -0
  32. data/lib/stella/test/run/summary.rb +70 -0
  33. data/lib/stella/test/stats.rb +114 -0
  34. data/lib/stella/text.rb +64 -0
  35. data/lib/stella/text/resource.rb +38 -0
  36. data/lib/utils/crypto-key.rb +84 -0
  37. data/lib/utils/domainutil.rb +47 -0
  38. data/lib/utils/escape.rb +302 -0
  39. data/lib/utils/fileutil.rb +78 -0
  40. data/lib/utils/httputil.rb +266 -0
  41. data/lib/utils/mathutil.rb +15 -0
  42. data/lib/utils/stats.rb +88 -0
  43. data/lib/utils/textgraph.rb +267 -0
  44. data/lib/utils/timerutil.rb +58 -0
  45. data/lib/win32/Console.rb +970 -0
  46. data/lib/win32/Console/ANSI.rb +305 -0
  47. data/support/kvm.h +91 -0
  48. data/support/ruby-pcap-takuma-notes.txt +19 -0
  49. data/support/ruby-pcap-takuma-patch.txt +30 -0
  50. data/support/text/en.yaml +80 -0
  51. data/support/text/nl.yaml +7 -0
  52. data/support/useragents.txt +75 -0
  53. data/tests/01-util_test.rb +0 -0
  54. data/tests/02-stella-util_test.rb +42 -0
  55. data/tests/10-stella_test.rb +104 -0
  56. data/tests/11-stella-storable_test.rb +68 -0
  57. data/tests/60-stella-command_test.rb +248 -0
  58. data/tests/80-stella-cli_test.rb +45 -0
  59. data/tests/spec-helper.rb +31 -0
  60. metadata +165 -0
@@ -0,0 +1,40 @@
1
+
2
+
3
+ module Stella::Command
4
+ class Base
5
+
6
+
7
+ # TODO: See EC2::Platform for example to improve/generalize platform
8
+ # discovery. We'll need this for monitoring.
9
+ IMPLEMENTATIONS = [
10
+ [/darwin/i, :unix, :macosx ]
11
+ ]
12
+ ARCHITECTURES = [
13
+ [/(i\d86)/i, :i386 ]
14
+ ]
15
+
16
+ # When using Stella::CLI this will contain the string used to call this command
17
+ # i.e. ab, siege, help, etc...
18
+ attr_accessor :shortname
19
+
20
+
21
+ def initialize()
22
+
23
+ #agent = find_agent(*expand_str(v))
24
+ #@logger.info(:cli_print_agent, agent) if @options.verbose >= 1
25
+
26
+ end
27
+
28
+ def run_sleeper(duration)
29
+ remainder = duration % 1
30
+ duration.to_i.times {
31
+ Stella::LOGGER.info_print('.') unless @quiet
32
+ sleep 1
33
+ }
34
+ sleep remainder if remainder > 0
35
+ end
36
+
37
+
38
+
39
+ end
40
+ end
@@ -0,0 +1,358 @@
1
+
2
+
3
+ module Stella
4
+ class LocalTest < Stella::Command::Base
5
+
6
+ # A container for the test parameters
7
+ attr_accessor :testdef
8
+ # The load tool adapter
9
+ attr_accessor :adapter
10
+
11
+ attr_accessor :test_path
12
+
13
+ attr_accessor :quiet
14
+ attr_accessor :guid
15
+ attr_accessor :verbose
16
+ attr_accessor :format
17
+
18
+ # list of all filesystem paths for each run in a single test
19
+ attr_reader :test_runpaths
20
+ # same as above, expect for all tests
21
+ attr_reader :all_runpaths
22
+ # list of all hostname:port in the test
23
+ attr_reader :hosts
24
+ # list of all /paths in the test
25
+ attr_reader :paths
26
+
27
+ attr_accessor :working_directory
28
+ attr_reader :available_agents
29
+ attr_reader :test_stats
30
+ attr_reader :rampup_test_stats
31
+
32
+ def initialize(testdef=nil, adapter=nil)
33
+ @testdef = testdef if testdef
34
+ @adapter = adapter if adapter
35
+
36
+ # Disabled until we resolve JRuby on OSX issue (won't load openssl)
37
+ #@guid = Crypto.sign(rand.to_s, rand.to_s)
38
+
39
+ @test_runpaths = []
40
+ @all_runpaths = []
41
+ @hosts = []
42
+ @paths = []
43
+ @format = 'yaml'
44
+ @agents = []
45
+ @verbose = 0
46
+
47
+ ua_path = File.join(STELLA_HOME, 'support', 'useragents.txt')
48
+ Stella::LOGGER.debug("LOADING #{ua_path}")
49
+
50
+ @available_agents = Stella::Util.process_useragents(ua_path)
51
+ end
52
+
53
+
54
+
55
+ def run
56
+
57
+
58
+ raise UnavailableAdapter.new(@adapter.name) unless @adapter.available?
59
+
60
+ # If the adapter isn't being called for a loadtest, we don't have anything to do.
61
+ unless @adapter.loadtest?
62
+ system(@adapter.command)
63
+ return
64
+ end
65
+
66
+ @agents = Stella::Util.find_agents(@available_agents, @testdef.agents)
67
+
68
+ @test_path = _generate_test_path
69
+
70
+ prepare_test(@test_path)
71
+
72
+ threshold = (@testdef.rampup) ? @testdef.rampup.ceiling : @adapter.vusers
73
+
74
+ runnum = "00"
75
+ while(@adapter.vusers <= threshold) do
76
+
77
+ # Make sure the load factor is set to 1. If there was a warmup,
78
+ # then this value will be less than 1.
79
+ testrun = maker_of_testruns(1)
80
+
81
+ # We empty test_runpaths so we can keep track of the runpaths for each
82
+ # level of virtual users. This is useful during a rampup test so we can
83
+ # collect the stats for the repetitions at each virtual user level.
84
+ @test_runpaths = []
85
+
86
+ # Execute each test run in turn. All of the steps are the same for each
87
+ # run. This is important because the purpose of the test runs is to
88
+ # create a statistical certainty of performance.
89
+ @testdef.repetitions.times do
90
+
91
+ # This value is used to create the run directory and show which run we're on
92
+ runnum.succ!
93
+
94
+ # Generate the filesystem path to store the run data.
95
+ runpath = File.join(test_path, "run#{runnum}")
96
+
97
+ # Keep track of every run path. We use this in postpare to
98
+ # do some stuff after all the tests have run
99
+ @all_runpaths << runpath
100
+
101
+ # test_runpaths will be identical to all_runpaths except when there is a
102
+ # rampup. In that case, test_runpaths will contain the runpaths for all
103
+ # repetitions of each level of virtual users.
104
+ @test_runpaths << runpath
105
+
106
+ testrun.call("Run #{runnum}", runpath)
107
+
108
+ # Only sleep if requested and there is more than 1 test run
109
+ if @testdef.sleep && @testdef.sleep.to_f > 0 && @testdef.repetitions > 1
110
+ run_sleeper(@testdef.sleep)
111
+ end
112
+ Stella::LOGGER.info('') unless @quiet # We want the newline
113
+ end
114
+
115
+ # Rampup tests produce multiple summary files which include the batch
116
+ # number. Regular runs have just one file we set here as the default.
117
+ summary_name = "STATS"
118
+
119
+ # It's possible for the interval to not divide evenly into the ceiling
120
+ # If we have room between the current number of virtual users and the
121
+ # ceiling, we'll add the difference for the final test run.
122
+ if (@testdef.rampup)
123
+ final_total = @adapter.vusers + @testdef.rampup.interval
124
+ if final_total > threshold && (threshold - @adapter.vusers) > 0
125
+ @adapter.vusers += (threshold - @adapter.vusers)
126
+ else
127
+ @adapter.vusers += @testdef.rampup.interval
128
+ end
129
+ padded_users = @adapter.vusers.to_s.rjust(4, '0')
130
+ summary_name << "-#{padded_users}"
131
+ end
132
+
133
+ # Read the run summaries for this batch and produce totals, averages,
134
+ # and standard deviations.
135
+ @test_stats = process_test_stats(@test_runpaths)
136
+ print_summary(test_stats) if (@testdef.repetitions > 1)
137
+ save_summary(File.join(test_path, "#{summary_name}.#{@format}"), @test_stats)
138
+
139
+
140
+ # Non-rampup tests only need to run through the loop once.
141
+ break if (!@testdef.rampup && @adapter.vusers == threshold)
142
+
143
+ end
144
+
145
+ if @testdef.rampup
146
+ # Notice the difference between these test stats and the ones above?
147
+ # These stats are based on the entire rampup test, across all levels
148
+ # of virtual users.
149
+ @rampup_test_stats = process_test_stats(@all_runpaths)
150
+ save_summary(File.join(test_path, "STATS.#{@format}"), @rampup_test_stats)
151
+ print_summary(test_stats) if (@testdef.repetitions > 1)
152
+ end
153
+ rescue Interrupt
154
+ exit
155
+ rescue AdapterError => ex
156
+ Stella::LOGGER.error(ex.message)
157
+ end
158
+
159
+ def test_path_symlink
160
+ return unless @working_directory
161
+ File.join(@working_directory, 'latest')
162
+ end
163
+
164
+
165
+ private
166
+
167
+ # prepare_test
168
+ #
169
+ # Execute the group of testruns associated to this test. This includes
170
+ # zero or one warmup and one or more testruns.
171
+ #
172
+ # INPUT:
173
+ # +test_path+ filesystem path to store all test data
174
+ #
175
+ def prepare_test(test_path)
176
+
177
+ Stella::LOGGER.info("Writing test data to: #{test_path}\n\n") unless @quiet
178
+
179
+ # Make sure the test storage directory is created along with the
180
+ # latest symlink
181
+ FileUtil.create_dir(test_path)
182
+ if Stella.sysinfo.os == :unix
183
+ File.unlink(test_path_symlink) if File.exists?(test_path_symlink)
184
+ File.symlink(File.expand_path(test_path), test_path_symlink)
185
+ end
186
+
187
+ # Write the test ID to the storage directory
188
+ # NOTE: Disabled until we resolve the issue with JRuby on OSX (won't load openssl)
189
+ #FileUtil.write_file(test_path + "/ID.txt", @guid, true)
190
+
191
+ # And the test run message
192
+ FileUtil.write_file(test_path + "/MESSAGE.txt", @testdef.message, true) if @testdef.message
193
+
194
+ @adapter.user_agent = @agents unless @agents.empty?
195
+
196
+ # The warmup is identical to a testrun except for two things:
197
+ # - we don't make note of the runpath
198
+ # - there is a one second sleep after the run.
199
+ # Everything else is identical.
200
+ if @testdef.warmup
201
+
202
+ testrun = maker_of_testruns(@testdef.warmup)
203
+
204
+ # Generate the filesystem path to store the run data.
205
+ # NOTE: We don't keep the warmup path in @test_runpaths because we
206
+ # include it in the final calculation for the test.
207
+ runpath = File.join(test_path, "warmup")
208
+
209
+ # Run the warmpup round
210
+ testrun.call("Warmup", runpath)
211
+
212
+ run_sleeper(@testdef.sleep || 1)
213
+
214
+ Stella::LOGGER.info('', '') unless @quiet # We just need the newline
215
+
216
+ end
217
+
218
+ print_title unless @quiet
219
+
220
+ end
221
+
222
+ # maker_of_testruns
223
+ #
224
+ # Generator of test runs. Everything that happens during a test
225
+ # run is defined here. We use a Proc so we can toss the functionality
226
+ # around like something that's dirty and loves a good tossing.
227
+ # It's important that all output be on a single line without a
228
+ # line terminator. Otherwise great descruction could occur.
229
+ def maker_of_testruns(factor)
230
+ testrun = Proc.new do |name,runpath|
231
+ # Make sure the test run storage directory is created
232
+ FileUtil.create_dir(runpath)
233
+
234
+ @adapter.load_factor = factor
235
+ @adapter.working_directory = runpath
236
+ @adapter.before
237
+
238
+ Stella::LOGGER.info_printf("%8s: %10d@%-6s ", name, @adapter.requests, @adapter.vuser_rate) unless @quiet
239
+
240
+ # Here we record the command arguments. This needs to be last because we modify
241
+ # some of the arguments above.
242
+ FileUtil.write_file(runpath + "/COMMAND.txt", @adapter.command, true)
243
+
244
+ # Execute the command, send STDOUT and STDERR to separate files.
245
+ command = "#{@adapter.command} 1> \"#{@adapter.stdout_path}\" 2> \"#{@adapter.stderr_path}\""
246
+ Stella::LOGGER.info(" COMMAND: #{command}") if @verbose >= 2
247
+
248
+ begin
249
+ # Call the load tool
250
+ # $? contains the error status
251
+ succeeded = system(command)
252
+
253
+ # TODO: Catch interrupts for system calls. Currently it will simply and and continue with the next command
254
+ # i.e. these don't work:
255
+ rescue Interrupt
256
+ exit
257
+ rescue SystemExit
258
+ exit
259
+ end
260
+
261
+ unless succeeded
262
+ Stella::LOGGER.info('', '') # We used print so we need a new line for the error message.
263
+ raise AdapterError.new(@adapter.name, @adapter.error)
264
+ end
265
+
266
+ @adapter.after
267
+
268
+ stats = @adapter.summary
269
+
270
+ save_summary(@adapter.summary_path(@format), stats)
271
+
272
+
273
+ if !@quiet && stats && stats.available?
274
+ Stella::LOGGER.info_print(sprintf("%3.0f%% %9.2f/s %8.3fs ", stats.availability || 0, stats.transaction_rate || 0, stats.response_time || 0))
275
+ Stella::LOGGER.info_print(sprintf("%8.3fMB/s %8.3fMB %8.3fs ", stats.throughput || 0, stats.data_transferred || 0, stats.elapsed_time || 0))
276
+ # NOTE: We don't print a line terminator here
277
+ end
278
+ end
279
+ end
280
+
281
+
282
+ # print_summary
283
+ #
284
+ # Called during the test after every batch of test runs. For a rampup test
285
+ # it's also called at the end of all the runs.
286
+ #
287
+ # INPUT:
288
+ # stats:: Any object that extends Stella::Test::Base object
289
+ def print_summary(stats)
290
+ return if stats.nil?
291
+ Stella::LOGGER.info(' ' << "-"*67) unless @quiet
292
+
293
+ Stella::LOGGER.info_printf("%8s: %10d@%-6d %3.0f%% %9.2f/s ", "Total", stats.transactions_total || 0, stats.vusers_avg || 0, stats.availability || 0, stats.transaction_rate_avg || 0)
294
+ Stella::LOGGER.info_printf("%8.3fs %8.3fMB/s %8.3fMB %8.3fs", stats.response_time_avg || 0, stats.throughput_avg || 0, stats.data_transferred_total || 0, stats.elapsed_time_total || 0)
295
+ Stella::LOGGER.info('') # New line
296
+ Stella::LOGGER.info_printf("%8s: %22s %9.2f/s %8.3fs %8.3fMB/s %10s %8.3fs", "Std Dev", '', stats.transaction_rate_sdev || 0, stats.response_time_sdev || 0, stats.throughput_sdev || 0, '', stats.elapsed_time_sdev || 0)
297
+ Stella::LOGGER.info('') # New line
298
+ Stella::LOGGER.info('') unless @quiet # Extra new line
299
+ end
300
+
301
+ # print_title
302
+ #
303
+ # Prints the column headers for the test run output. Field widths match those
304
+ # in print_summary and test_maker
305
+ def print_title
306
+ Stella::LOGGER.info(' ' << "-"*67) unless @quiet
307
+
308
+ Stella::LOGGER.info_printf("%8s %10s@%-5s %5s %11s", "", 'REQ', 'VU/s', 'AVAIL', 'REQ/s')
309
+ Stella::LOGGER.info_printf("%10s %12s %10s %9s", 'RTIME', 'DATA/s', 'DATA', 'TIME')
310
+ Stella::LOGGER.info('') # New line
311
+ Stella::LOGGER.info('') unless @quiet # Extra new line
312
+ end
313
+
314
+ # save_summary
315
+ #
316
+ # Write a summary object to disk
317
+ #
318
+ # INPUT:
319
+ # filepath:: the complete path for the file (string)
320
+ # stats:: Any object that extends Stella::Test::Base object
321
+ def save_summary(filepath, stats)
322
+ return unless stats
323
+ stats.format = @format
324
+ stats.to_file(filepath)
325
+ end
326
+
327
+ # Load SUMMARY file for each run and create a summary with
328
+ # totals, averages, and standard deviations.
329
+ def process_test_stats(paths)
330
+ return unless paths && !paths.empty?
331
+ test_stats = Stella::Test::Stats.new(@message)
332
+
333
+ paths.each do |path|
334
+ next unless File.exists?("#{path}/SUMMARY.#{@format}")
335
+ test_run = Stella::Test::Run::Summary.from_file("#{path}/SUMMARY.#{@format}")
336
+ test_stats.add_run(test_run)
337
+ end
338
+
339
+ test_stats
340
+ end
341
+
342
+ # This is the path where all test data will be stored
343
+ # The default is testruns/2008-12-31/test-001
344
+ def _generate_test_path
345
+ now = DateTime.now
346
+ time = Time.now
347
+ daystr = "#{now.year}-#{now.mon.to_s.rjust(2,'0')}-#{now.mday.to_s.rjust(2,'0')}"
348
+ testpath = File.join(@working_directory, 'testruns', daystr, 'test-')
349
+ testnum = 1.to_s.rjust(3,'0')
350
+ testnum.succ! while(File.directory? "#{testpath}#{testnum}")
351
+ "#{testpath}#{testnum}"
352
+ end
353
+
354
+
355
+
356
+ end
357
+ end
358
+
@@ -0,0 +1,82 @@
1
+
2
+
3
+ module Stella::Data
4
+ class DomainRequest < Stella::Storable
5
+ attr_accessor :dns_data
6
+ attr_reader :raw_data
7
+
8
+ field :time => DateTime
9
+ field :client_ip => String
10
+ field :server_ip => String
11
+ field :domain_name => String
12
+ field :header => String
13
+
14
+ def initialize(raw_data)
15
+ @raw_data = raw_data
16
+ @dns_data, @domain_name, @header = DomainUtil::parse_domain_request(@raw_data)
17
+ end
18
+
19
+ def has_request?
20
+ false
21
+ end
22
+ def has_response?
23
+ false
24
+ end
25
+ def has_body?
26
+ false
27
+ end
28
+
29
+ def to_s
30
+ "%s: %s -> %s (%s)" % [@time, @client_ip, @server_ip, @domain_name]
31
+ end
32
+
33
+ def inspect
34
+ str = "#{$/};; REQUEST #{@time.to_s}"
35
+ str << "#{$/};; %s %s> %s" % [@client_ip, '-'*30, @server_ip]
36
+ str << "#{$/};;#{$/}"
37
+ str << @dns_data.inspect
38
+ str
39
+ end
40
+
41
+ end
42
+
43
+ class DomainResponse < Stella::Storable
44
+ attr_accessor :dns_data
45
+ attr_reader :raw_data
46
+
47
+ field :time => DateTime
48
+ field :client_ip => String
49
+ field :server_ip => String
50
+ field :domain_name => String
51
+ field :header => String
52
+ field :addresses => Array
53
+ field :cnames => Array
54
+
55
+
56
+ def initialize(raw_data)
57
+ @raw_data = raw_data
58
+ @dns_data, @domain_name, @header, @addresses, @cnames = DomainUtil::parse_domain_response(@raw_data)
59
+ end
60
+
61
+ def has_request?
62
+ false
63
+ end
64
+ def has_response?
65
+ false
66
+ end
67
+ def has_body?
68
+ false
69
+ end
70
+
71
+ def to_s
72
+ "%s: %s <- %s (%s) %s" % [@time, @client_ip, @server_ip, @domain_name, (@addresses || []).join(',')]
73
+ end
74
+
75
+ def inspect
76
+ str = "#{$/};; RESPONSE #{@time.strftime(NICE_TIME_FORMAT)}"
77
+ str << "#{$/};; %s <%s %s" % [@client_ip, '-'*30, @server_ip]
78
+ str << "#{$/};;#{$/}"
79
+ str << @dns_data.inspect
80
+ end
81
+ end
82
+ end