stella 0.3.2

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 (56) hide show
  1. data/README.txt +135 -0
  2. data/Rakefile +100 -0
  3. data/bin/stella +12 -0
  4. data/lib/stella.rb +58 -0
  5. data/lib/stella/adapter/ab.rb +303 -0
  6. data/lib/stella/adapter/base.rb +87 -0
  7. data/lib/stella/adapter/httperf.rb +296 -0
  8. data/lib/stella/adapter/siege.rb +321 -0
  9. data/lib/stella/cli.rb +291 -0
  10. data/lib/stella/cli/agents.rb +73 -0
  11. data/lib/stella/cli/base.rb +19 -0
  12. data/lib/stella/cli/language.rb +18 -0
  13. data/lib/stella/cli/localtest.rb +80 -0
  14. data/lib/stella/command/base.rb +111 -0
  15. data/lib/stella/command/localtest.rb +339 -0
  16. data/lib/stella/logger.rb +63 -0
  17. data/lib/stella/response.rb +82 -0
  18. data/lib/stella/storable.rb +116 -0
  19. data/lib/stella/support.rb +106 -0
  20. data/lib/stella/test/base.rb +34 -0
  21. data/lib/stella/test/definition.rb +79 -0
  22. data/lib/stella/test/run/summary.rb +50 -0
  23. data/lib/stella/test/summary.rb +82 -0
  24. data/lib/stella/text.rb +64 -0
  25. data/lib/stella/text/resource.rb +39 -0
  26. data/lib/utils/crypto-key.rb +84 -0
  27. data/lib/utils/escape.rb +302 -0
  28. data/lib/utils/fileutil.rb +59 -0
  29. data/lib/utils/httputil.rb +210 -0
  30. data/lib/utils/mathutil.rb +78 -0
  31. data/lib/utils/textgraph.rb +267 -0
  32. data/lib/utils/timerutil.rb +58 -0
  33. data/support/text/en.yaml +54 -0
  34. data/support/text/nl.yaml +1 -0
  35. data/support/useragents.txt +75 -0
  36. data/vendor/useragent/MIT-LICENSE +20 -0
  37. data/vendor/useragent/README +21 -0
  38. data/vendor/useragent/init.rb +1 -0
  39. data/vendor/useragent/lib/user_agent.rb +83 -0
  40. data/vendor/useragent/lib/user_agent/browsers.rb +24 -0
  41. data/vendor/useragent/lib/user_agent/browsers/all.rb +69 -0
  42. data/vendor/useragent/lib/user_agent/browsers/gecko.rb +43 -0
  43. data/vendor/useragent/lib/user_agent/browsers/internet_explorer.rb +40 -0
  44. data/vendor/useragent/lib/user_agent/browsers/opera.rb +49 -0
  45. data/vendor/useragent/lib/user_agent/browsers/webkit.rb +94 -0
  46. data/vendor/useragent/lib/user_agent/comparable.rb +25 -0
  47. data/vendor/useragent/lib/user_agent/operating_systems.rb +19 -0
  48. data/vendor/useragent/spec/browsers/gecko_user_agent_spec.rb +209 -0
  49. data/vendor/useragent/spec/browsers/internet_explorer_user_agent_spec.rb +99 -0
  50. data/vendor/useragent/spec/browsers/opera_user_agent_spec.rb +59 -0
  51. data/vendor/useragent/spec/browsers/other_user_agent_spec.rb +19 -0
  52. data/vendor/useragent/spec/browsers/webkit_user_agent_spec.rb +373 -0
  53. data/vendor/useragent/spec/spec_helper.rb +1 -0
  54. data/vendor/useragent/spec/user_agent_spec.rb +331 -0
  55. data/vendor/useragent/useragent.gemspec +12 -0
  56. metadata +139 -0
@@ -0,0 +1,111 @@
1
+
2
+
3
+ module Stella::Command
4
+ class Base
5
+
6
+ BrowserNicks = {
7
+ 'ff' => 'firefox',
8
+ 'ie' => 'internetexplorer'
9
+ }.freeze unless defined? BrowserNicks
10
+
11
+ OperatingSystemNicks = {
12
+ 'win' => 'windows',
13
+ 'lin' => 'linux',
14
+ 'osx' => 'osx',
15
+ 'freebsd' => 'bsd',
16
+ 'netbsd' => 'bsd',
17
+ 'openbsd' => 'bsd'
18
+ }.freeze unless defined? OperatingSystemNicks
19
+
20
+ # TODO: See EC2::Platform for example to improve/generalize platform
21
+ # discovery. We'll need this for monitoring.
22
+ IMPLEMENTATIONS = [
23
+ [/darwin/i, :unix, :macosx ]
24
+ ]
25
+ ARCHITECTURES = [
26
+ [/(i\d86)/i, :i386 ]
27
+ ]
28
+
29
+ # When using Stella::CLI this will contain the string used to call this command
30
+ # i.e. ab, siege, help, etc...
31
+ attr_accessor :shortname
32
+
33
+
34
+ def initialize()
35
+
36
+ #agent = find_agent(*expand_str(v))
37
+ #@logger.info(:cli_print_agent, agent) if @options.verbose >= 1
38
+
39
+ end
40
+
41
+ def run_sleeper(duration)
42
+ remainder = duration % 1
43
+ duration.to_i.times {
44
+ Stella::LOGGER.info_print('.') unless @quiet
45
+ sleep 1
46
+ }
47
+ sleep remainder if remainder > 0
48
+ end
49
+
50
+ # find_agent
51
+ #
52
+ # Takes an input string which can be either a shortname or a complete
53
+ # user agent string. If the string matches the shortname format, it
54
+ # will select an agent string from useragents.txt based on the shortname.
55
+ # Shortname takes the following format: browser-version-os.
56
+ # Examples: ff-3-linux, ie-5, opera-10-win, chrome-0.2-osx, random
57
+ # If os doesn't match, it will look for the browser and version. If it can't
58
+ # find the version it will look for the browser and apply the version given.
59
+ # If browser doesn't match a known browser, it assumes the string is a
60
+ # complete user agent and simply returns that value.
61
+ def find_agent(name,second=nil,third=nil)
62
+ name = (BrowserNicks.has_key?(name)) ? BrowserNicks[name] : name
63
+ return name unless @available_agents.has_key?(name) || name == "random"
64
+
65
+ index = name
66
+ if (second && third) # i.e. opera-9-osx
67
+ os = (OperatingSystemNicks.has_key?(third)) ? OperatingSystemNicks[third] : third
68
+ index = "#{name}-#{second}-#{os}"
69
+ elsif(second && second.to_i > 0) # i.e. opera-9
70
+ index = "#{name}-#{second}"
71
+ elsif(second) # i.e. opera-osx
72
+ os = (OperatingSystemNicks.has_key?(second)) ? OperatingSystemNicks[second] : second
73
+ index = "#{name}-#{os}"
74
+ elsif(name == "random")
75
+ index = @available_agents.keys[ rand(@available_agents.keys.size) ]
76
+ end
77
+
78
+ # Attempt to find a pool of user agents that match the supplied index
79
+ ua_pool = @available_agents[index]
80
+
81
+ # In the event we don't find an agent above (which will only happen
82
+ # when the user provided a version), we'll take a random agent for
83
+ # the same browser and apply the version supplied by the user. We
84
+ # create the index using just the major version number so if the user
85
+ # supplies a specific verswion number, they will always end up here.
86
+ unless ua_pool
87
+ os = (OperatingSystemNicks.has_key?(third)) ? OperatingSystemNicks[third] : third
88
+ index = (os) ? "#{name}-#{os}" : name
89
+ ua_tmp = @available_agents[index][ rand(@available_agents[index].size) ]
90
+ ua_tmp.version = second if second.to_i > 0
91
+ ua_pool = [ua_tmp]
92
+ end
93
+
94
+ ua = ua_pool[ rand(ua_pool.size) ]
95
+
96
+ ua.to_s
97
+
98
+ end
99
+
100
+
101
+ #
102
+ # Generates a string of random alphanumeric characters
103
+ # These are used as IDs throughout the system
104
+ def strand( len )
105
+ chars = ("a".."z").to_a + ("0".."9").to_a
106
+ newpass = ""
107
+ 1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
108
+ return newpass
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,339 @@
1
+
2
+
3
+ module Stella
4
+ class LocalTest < Stella::Command::Base
5
+
6
+
7
+
8
+ # A container for the test parameters
9
+ attr_accessor :testdef
10
+ # The load tool adapter
11
+ attr_accessor :adapter
12
+
13
+ attr_accessor :test_path
14
+
15
+ attr_accessor :quiet
16
+ attr_accessor :guid
17
+ attr_accessor :verbose
18
+ attr_accessor :format
19
+
20
+ # list of all filesystem paths for each run in a single test
21
+ attr_reader :test_runpaths
22
+ # same as above, expect for all tests
23
+ attr_reader :all_runpaths
24
+ # list of all hostname:port in the test
25
+ attr_reader :hosts
26
+ # list of all /paths in the test
27
+ attr_reader :paths
28
+
29
+ attr_accessor :working_directory
30
+ attr_reader :available_agents
31
+
32
+ def initialize(testdef=nil, adapter=nil)
33
+ @testdef = testdef if testdef
34
+ @adapter = adapter if adapter
35
+
36
+ @guid = Crypto.sign(rand.to_s, rand.to_s)
37
+
38
+ @test_runpaths = []
39
+ @all_runpaths = []
40
+ @hosts = []
41
+ @paths = []
42
+ @format = 'yaml'
43
+ @agents = []
44
+
45
+ @available_agents = index_available_agents(File.join(STELLA_HOME, 'support', 'useragents.txt'))
46
+ end
47
+
48
+ def index_available_agents(path)
49
+ Stella::LOGGER.debug("LOADING #{path}")
50
+ return [] unless File.exists?(path)
51
+ Stella::Util.process_useragents(FileUtil.read_file_to_array(path))
52
+ end
53
+
54
+ def translate_requested_agents(possible_agents=[])
55
+ agents = []
56
+ possible_agents.each do |a|
57
+ agents << find_agent(*a)
58
+ end
59
+ agents
60
+ end
61
+
62
+ def run
63
+
64
+
65
+ raise UnavailableAdapter.new(@adapter.name) unless @adapter.available?
66
+
67
+ unless @adapter.loadtest?
68
+ system(@adapter.command)
69
+
70
+ return
71
+ end
72
+
73
+ @agents = translate_requested_agents(@testdef.agents)
74
+
75
+ test_path = _generate_test_path
76
+
77
+ prepare_test(test_path)
78
+
79
+ threshold = (@testdef.rampup) ? @testdef.rampup.ceiling : @adapter.vusers
80
+
81
+ runnum = "00"
82
+ while(@adapter.vusers <= threshold) do
83
+
84
+ # Make sure the load factor is set to 1. If there was a warmup,
85
+ # then this value will be less than 1.
86
+ testrun = maker_of_testruns(1)
87
+
88
+ # We empty test_runpaths so we can keep track of the runpaths for each
89
+ # level of virtual users. This is useful during a rampup test so we can
90
+ # collect the stats for the repetitions at each virtual user level.
91
+ @test_runpaths = []
92
+
93
+ # Execute each test run in turn. All of the steps are the same for each
94
+ # run. This is important because the purpose of the test runs is to
95
+ # create a statistical certainty of performance.
96
+ @testdef.repetitions.times do
97
+
98
+ # This value is used to create the run directory and show which run we're on
99
+ runnum.succ!
100
+
101
+ # Generate the filesystem path to store the run data.
102
+ runpath = File.join(test_path, "run#{runnum}")
103
+
104
+ # Keep track of every run path. We use this in postpare to
105
+ # do some stuff after all the tests have run
106
+ @all_runpaths << runpath
107
+
108
+ # test_runpaths will be identical to all_runpaths except when there is a
109
+ # rampup. In that case, test_runpaths will contain the runpaths for all
110
+ # repetitions of each level of virtual users.
111
+ @test_runpaths << runpath
112
+
113
+ testrun.call("Run #{runnum}", runpath)
114
+
115
+ # Only sleep if requested and there is more than 1 test run
116
+ if @testdef.sleep && @testdef.sleep.to_f > 0 && @testdef.repetitions > 1
117
+ run_sleeper(@testdef.sleep)
118
+ end
119
+ Stella::LOGGER.info('') unless @quiet # We want the newline
120
+ end
121
+
122
+ # Rampup tests produce multiple summary files which include the batch
123
+ # number. Regular runs have just one file we set here as the default.
124
+ summary_name = "SUMMARY"
125
+
126
+ # It's possible for the interval to not divide evenly into the ceiling
127
+ # If we have room between the current number of virtual users and the
128
+ # ceiling, we'll add the difference for the final test run.
129
+ if (@testdef.rampup)
130
+ final_total = @adapter.vusers + @testdef.rampup.interval
131
+ if final_total > threshold && (threshold - @adapter.vusers) > 0
132
+ @adapter.vusers += (threshold - @adapter.vusers)
133
+ else
134
+ @adapter.vusers += @testdef.rampup.interval
135
+ end
136
+ padded_users = @adapter.vusers.to_s.rjust(4, '0')
137
+ summary_name = "SUMMARY-#{padded_users}"
138
+ end
139
+
140
+ # Read the run summaries for this batch and produce totals, averages,
141
+ # and standard deviations.
142
+ test_stats = process_test_stats(@test_runpaths)
143
+ print_summary(test_stats) if (@testdef.repetitions > 1)
144
+ save_summary(File.join(test_path, "#{summary_name}.#{@format}"), test_stats)
145
+
146
+
147
+ # Non-rampup tests only need to run through the loop once.
148
+ break if (!@testdef.rampup && @adapter.vusers == threshold)
149
+
150
+ end
151
+
152
+ if @testdef.rampup
153
+ # Notice the difference between these test stats and the ones above?
154
+ # These stats are based on the entire rampup test, across all levels
155
+ # of virtual users.
156
+ test_stats = process_test_stats(@all_runpaths)
157
+ save_summary(File.join(test_path, "SUMMARY-RAMPUP.#{@format}"), test_stats)
158
+ print_summary(test_stats) if (@testdef.repetitions > 1)
159
+ end
160
+ end
161
+
162
+ def latest_test_symlink_path
163
+ return unless @working_directory
164
+ File.join(@working_directory, 'latest')
165
+ end
166
+
167
+ private
168
+
169
+ # prepare_test
170
+ #
171
+ # Execute the group of testruns associated to this test. This includes
172
+ # zero or one warmup and one or more testruns.
173
+ #
174
+ # INPUT:
175
+ # +test_path+ filesystem path to store all test data
176
+ #
177
+ def prepare_test(test_path)
178
+
179
+ Stella::LOGGER.info("Writing test data to: #{test_path}\n\n") unless @quiet
180
+
181
+ # Make sure the test storage directory is created along with the
182
+ # latest symlink
183
+ FileUtil.create_dir(test_path)
184
+ File.unlink(latest_test_symlink_path) if File.exists?(latest_test_symlink_path)
185
+ File.symlink(File.expand_path(test_path), latest_test_symlink_path)
186
+
187
+ # Write the test ID to the storage directory
188
+ FileUtil.write_file(test_path + "/ID.txt", @guid, true)
189
+
190
+ # And the test run message
191
+ FileUtil.write_file(test_path + "/MESSAGE.txt", @testdef.message, true) if @testdef.message
192
+
193
+ @adapter.user_agent = @agents unless @agents.empty?
194
+
195
+ # The warmup is identical to a testrun except for two things:
196
+ # - we don't make note of the runpath
197
+ # - there is a one second sleep after the run.
198
+ # Everything else is identical.
199
+ if @testdef.warmup
200
+
201
+ testrun = maker_of_testruns(@testdef.warmup)
202
+
203
+ # Generate the filesystem path to store the run data.
204
+ # NOTE: We don't keep the warmup path in @test_runpaths because we
205
+ # include it in the final calculation for the test.
206
+ runpath = File.join(test_path, "warmup")
207
+
208
+ # Run the warmpup round
209
+ testrun.call("Warmup", runpath)
210
+
211
+ run_sleeper(@testdef.sleep || 1)
212
+
213
+ Stella::LOGGER.info('', '') unless @quiet # We just need the newline
214
+
215
+ end
216
+
217
+ print_title unless @quiet
218
+
219
+ end
220
+
221
+ # maker_of_testruns
222
+ #
223
+ # Generator of test runs. Everything that happens during a test
224
+ # run is defined here. We use a Proc so we can toss the functionality
225
+ # around like something that's dirty and loves a good tossing.
226
+ # It's important that all output be on a single line without a
227
+ # line terminator. Otherwise great descruction could occur.
228
+ def maker_of_testruns(factor)
229
+ testrun = Proc.new do |name,runpath|
230
+ # Make sure the test run storage directory is created
231
+ FileUtil.create_dir(runpath)
232
+
233
+ @adapter.load_factor = factor
234
+ @adapter.working_directory = runpath
235
+ @adapter.before
236
+
237
+ Stella::LOGGER.info_printf("%8s: %10d@%-6s ", name, @adapter.requests, @adapter.vuser_rate) unless @quiet
238
+
239
+ # Here we record the command arguments. This needs to be last because we modify
240
+ # some of the arguments above.
241
+ FileUtil.write_file(runpath + "/COMMAND.txt", @adapter.command, true)
242
+
243
+ # Execute the command, send STDOUT and STDERR to separate files.
244
+ command = "#{@adapter.command} 1> '#{@adapter.stdout_path}' 2> '#{@adapter.stderr_path}'"
245
+ Stella::LOGGER.info(" COMMAND: #{command}") if @verbose >= 2
246
+
247
+ # Call the load tool
248
+ # for Windows, see: http://blog.crankybit.com/redirecting-output-to-a-file-in-windows-batch-scripts/
249
+ # http://weblogs.asp.net/lorenh/archive/2006/03/24/441004.aspx
250
+ system(command)
251
+
252
+ stats = @adapter.stats
253
+
254
+ save_summary(@adapter.summary_path(@format), stats)
255
+
256
+
257
+ unless @quiet
258
+ Stella::LOGGER.info_print(sprintf("%3.0f%% %9.2f/s %8.3fs ", stats.availability, stats.transaction_rate, stats.response_time))
259
+ Stella::LOGGER.info_print(sprintf("%8.3fMB/s %8.3fMB %8.3fs ", stats.throughput, stats.data_transferred, stats.elapsed_time))
260
+ # NOTE: We don't print a line terminator here
261
+ end
262
+
263
+ end
264
+ end
265
+
266
+
267
+ # print_summary
268
+ #
269
+ # Called during the test after every batch of test runs. For a rampup test
270
+ # it's also called at the end of all the runs.
271
+ #
272
+ # INPUT:
273
+ # stats:: Any object that extends Stella::Test::Base object
274
+ def print_summary(stats)
275
+ Stella::LOGGER.info(' ' << "-"*67) unless @quiet
276
+
277
+ Stella::LOGGER.info_printf("%8s: %10d@%-6d% 3.0f%% %9.2f/s ", "Total", stats.transactions_total, stats.vusers_avg, stats.availability, stats.transaction_rate_avg)
278
+ Stella::LOGGER.info_printf("%8.3fs %8.3fMB/s %8.3fMB %8.3fs", stats.response_time_avg, stats.throughput_avg, stats.data_transferred_total, stats.elapsed_time_total)
279
+ Stella::LOGGER.info('') # New line
280
+ Stella::LOGGER.info_printf("%8s: %22s %9.2f/s %8.3fs %8.3fMB/s %10s %8.3fs", "Std Dev", '', stats.transaction_rate_sdev, stats.response_time_sdev, stats.throughput_sdev, '', stats.elapsed_time_sdev)
281
+ Stella::LOGGER.info('') # New line
282
+ Stella::LOGGER.info('') unless @quiet # Extra new line
283
+ end
284
+
285
+ # print_title
286
+ #
287
+ # Prints the column headers for the test run output. Field widths match those
288
+ # in print_summary and test_maker
289
+ def print_title
290
+ Stella::LOGGER.info(' ' << "-"*67) unless @quiet
291
+
292
+ Stella::LOGGER.info_printf("%8s %10s@%-5s %5s %11s", "", 'REQ', 'VU/s', 'AVAIL', 'REQ/s')
293
+ Stella::LOGGER.info_printf("%10s %12s %10s %9s", 'RTIME', 'DATA/s', 'DATA', 'TIME')
294
+ Stella::LOGGER.info('') # New line
295
+ Stella::LOGGER.info('') unless @quiet # Extra new line
296
+ end
297
+
298
+ # save_summary
299
+ #
300
+ # Write a summary object to disk
301
+ #
302
+ # INPUT:
303
+ # filepath:: the complete path for the file (string)
304
+ # stats:: Any object that extends Stella::Test::Base object
305
+ def save_summary(filepath, stats)
306
+ FileUtil.write_file(filepath, stats.dump(@format, :with_titles), true)
307
+ end
308
+
309
+ # Load SUMMARY file for each run and create a summary with
310
+ # totals, averages, and standard deviations.
311
+ def process_test_stats(paths)
312
+ test_stats = Stella::Test::Summary.new(@message)
313
+ all_stats_obj = []
314
+ paths.each do |path|
315
+ file_contents = FileUtil.read_file_to_array("#{path}/SUMMARY.#{@format}")
316
+ stats = Stella::Test::Run::Summary.undump(@format, file_contents)
317
+ stats_obj = Stella::Test::Run::Summary.from_hash(stats)
318
+ test_stats.add_run(stats_obj)
319
+ end
320
+
321
+ test_stats
322
+ end
323
+
324
+ # This is the path where all test data will be stored
325
+ # The default is testruns/2008-12-31/test-001
326
+ def _generate_test_path
327
+ now = DateTime.now
328
+ time = Time.now
329
+ daystr = "#{now.year}-#{now.mon.to_s.rjust(2,'0')}-#{now.mday.to_s.rjust(2,'0')}"
330
+ testpath = File.join(@working_directory, 'testruns', daystr, 'test-')
331
+ testnum = 1.to_s.rjust(3,'0')
332
+ testnum.succ! while(File.directory? "#{testpath}#{testnum}")
333
+ "#{testpath}#{testnum}"
334
+ end
335
+
336
+
337
+ end
338
+ end
339
+