solutious-stella 0.5.5

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 (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