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,276 @@
1
+
2
+
3
+
4
+ module Stella
5
+
6
+ class InvalidArgument < RuntimeError
7
+ attr_accessor :name
8
+ def initialize(name)
9
+ @name = name
10
+ end
11
+ def message
12
+ Stella::TEXT.err(:error_invalid_argument, @name)
13
+ end
14
+
15
+ end
16
+
17
+ class UnavailableAdapter < RuntimeError
18
+ attr_accessor :name
19
+ def initialize(name)
20
+ @name = name
21
+ end
22
+ def message
23
+ Stella::TEXT.err(:error_unavailable_adapter, @name)
24
+ end
25
+ end
26
+
27
+ class UnknownValue < RuntimeError
28
+ attr_accessor :value
29
+ def initialize(value)
30
+ @value = value.to_s
31
+ end
32
+ def message
33
+ Stella::TEXT.err(:error_unknown_value, @value)
34
+ end
35
+ end
36
+
37
+ class UnsupportedLanguage < RuntimeError
38
+ end
39
+
40
+ class MissingDependency < RuntimeError
41
+ attr_accessor :dependency, :reason
42
+ def initialize(dependency, reason=:error_generic)
43
+ @dependency = dependency
44
+ @reason = (reason.kind_of? Symbol) ? Stella::TEXT.err(reason) : reason
45
+ end
46
+ def message
47
+ Stella::TEXT.err(:error_missing_dependency, @dependency, @reason)
48
+ end
49
+ end
50
+
51
+ class AdapterError < MissingDependency
52
+ def initialize(adapter, reason=:error_generic)
53
+ @adapter = adapter
54
+ @reason = (reason.kind_of? Symbol) ? Stella::TEXT.err(reason) : reason
55
+ end
56
+ def message
57
+ Stella::TEXT.err(:error_adapter_runtime, @adapter, @reason)
58
+ end
59
+ end
60
+
61
+ class Util
62
+
63
+ BrowserNicks = {
64
+ :ff => 'firefox',
65
+ :ie => 'internetexplorer'
66
+ }.freeze unless defined? BrowserNicks
67
+
68
+ OperatingSystemNicks = {
69
+ :win => 'windows',
70
+ :lin => 'linux',
71
+ :osx => 'osx',
72
+ :freebsd => 'bsd',
73
+ :netbsd => 'bsd',
74
+ :openbsd => 'bsd'
75
+ }.freeze unless defined? OperatingSystemNicks
76
+
77
+ # process_useragents
78
+ #
79
+ # We read the useragents.txt file into a hash index which
80
+ # useful for refering to specific useragents on the command-line
81
+ # and other places.
82
+ #
83
+ # Examples:
84
+ # --agent=ie-5
85
+ # --agent=ff-2.0.0.2-linux
86
+ # --agent=chrome-windows
87
+ # --agent=safari-3.0-osx
88
+ #
89
+ def self.process_useragents(path=nil)
90
+ raise "Cannot find #{path}" unless File.exists? path
91
+ ua_strs = FileUtil.read_file_to_array(path)
92
+ return {} if ua_strs.empty?
93
+
94
+ agents_index = {}
95
+ ua_strs.each do |ua_str|
96
+ ua_str.chomp! # remove trailing line separator
97
+
98
+ ua = UserAgent.parse(ua_str)
99
+
100
+ # Standardize the index values
101
+ # i.e. firefox-3-windows
102
+ name = ua.browser.downcase.tr(' ', '')
103
+ version = ua.version.to_i
104
+ os = ua.platform.downcase.tr(' ', '')
105
+
106
+ # Non-windows operating systems have the OS string inside of "os"
107
+ # rather than "platform". We look there for the value and then
108
+ # standardize the values.
109
+ # i.e. firefox-3-osx
110
+ if os != 'windows'
111
+ os = ua.os.downcase
112
+ os = 'linux' if os.match(/^linux/)
113
+ os = 'osx' if os.match(/mac os x/)
114
+ os = 'bsd' if os.match(/bsd/)
115
+ end
116
+
117
+ # Make sure all arrays exist before we populate them
118
+ agents_index[name] ||= []
119
+ agents_index["#{name}-#{version}"] ||= []
120
+ agents_index["#{name}-#{version}-#{os}"] ||= []
121
+ agents_index["#{name}-#{os}"] ||= [] # We use this one for failover
122
+
123
+ # Populate each list.
124
+ agents_index[name] << ua
125
+ agents_index["#{name}-#{version}"] << ua
126
+ agents_index["#{name}-#{version}-#{os}"] << ua
127
+ agents_index["#{name}-#{os}"] << ua
128
+
129
+ end
130
+
131
+ agents_index
132
+ end
133
+
134
+ def self.find_agents(agent_list, possible_agents=[])
135
+ return [] if agent_list.nil? || agent_list.empty?
136
+ return [] if possible_agents.nil? || possible_agents.empty?
137
+
138
+ agents = []
139
+ possible_agents.each do |a|
140
+ agents << Stella::Util.find_agent(agent_list, *a)
141
+ end
142
+
143
+ agents
144
+ end
145
+
146
+ # find_agent
147
+ #
148
+ # Takes an input string which can be either a shortname or a complete
149
+ # user agent string. If the string matches the shortname format, it
150
+ # will select an agent string from useragents.txt based on the shortname.
151
+ # Shortname takes the following format: browser-version-os.
152
+ # Examples: ff-3-linux, ie-5, opera-10-win, chrome-0.2-osx, random
153
+ # If os doesn't match, it will look for the browser and version. If it can't
154
+ # find the version it will look for the browser and apply the version given.
155
+ # If browser doesn't match a known browser, it assumes the string is a
156
+ # complete user agent and simply returns that value.
157
+ def self.find_agent(agent_list, name,second=nil,third=nil)
158
+ return '' if agent_list.nil? || agent_list.empty?
159
+ name = (BrowserNicks.has_key?(name.to_s.to_sym)) ? BrowserNicks[name.to_s.to_sym] : name
160
+ return name unless agent_list.has_key?(name) || name == "random"
161
+
162
+ index = name
163
+ if (second && third) # i.e. opera-9-osx
164
+ os = (OperatingSystemNicks.has_key?(third)) ? OperatingSystemNicks[third] : third
165
+ index = "#{name}-#{second}-#{os}"
166
+ elsif(second && second.to_i > 0) # i.e. opera-9
167
+ index = "#{name}-#{second}"
168
+ elsif(second) # i.e. opera-osx
169
+ os = (OperatingSystemNicks.has_key?(second)) ? OperatingSystemNicks[second] : second
170
+ index = "#{name}-#{os}"
171
+ elsif(name == "random")
172
+ index = agent_list.keys[ rand(agent_list.keys.size) ]
173
+ end
174
+
175
+ # Attempt to find a pool of user agents that match the supplied index
176
+ ua_pool = agent_list[index]
177
+
178
+ # In the event we don't find an agent above (which will only happen
179
+ # when the user provided a version), we'll take a random agent for
180
+ # the same browser and apply the version supplied by the user. We
181
+ # create the index using just the major version number so if the user
182
+ # supplies a specific verswion number, they will always end up here.
183
+ unless ua_pool
184
+ os = (OperatingSystemNicks.has_key?(third)) ? OperatingSystemNicks[third] : third
185
+ index = (os) ? "#{name}-#{os}" : name
186
+ ua_tmp = agent_list[index][ rand(agent_list[index].size) ]
187
+ ua_tmp.version = second if second.to_i > 0
188
+ ua_pool = [ua_tmp]
189
+ end
190
+
191
+ ua = ua_pool[ rand(ua_pool.size) ]
192
+
193
+ ua.to_s
194
+
195
+ end
196
+
197
+ # expand_str
198
+ #
199
+ # Turns a string like ff-4-freebsd into ["ff","4","freebsd"]
200
+ # We use this for command-line values liek agent and rampup.
201
+ # +str+ is a comma or dash separated string.
202
+ # +type+ is a class type to cast to (optional, default: String)
203
+ def self.expand_str(str, type=String)
204
+ # this removes extra spaces along with the comma
205
+ str.split(/\s*[,\-]\s*/).inject([]) do |list,value| list << eval("#{type}('#{value}')") end
206
+ end
207
+
208
+
209
+ # capture_output
210
+ #
211
+ # +cmd+ is a shell command to run with Kernel.` It will be appended with
212
+ # redirects to send STDOUT and STDERR to temp files. If the command already
213
+ # contains redirects they will be removed and replaced.
214
+ # The tempfiles are sent to yield as arrays of lines (using file_sout.readlines)
215
+ # and deleted before returning.
216
+ #
217
+ # We use files because popen and open3 are not implemented on Windows.
218
+ def self.capture_output(cmd)
219
+ return unless cmd
220
+ file_sout = Tempfile.new("stdout" << strand(6)) # We add a strand to be super sure it doesn't exist
221
+ file_serr = Tempfile.new("stderr" << strand(6))
222
+ cmd.gsub!(/1>.+/, '')
223
+ cmd.gsub!(/2>.+/, '')
224
+ cmd = "#{cmd} 1> \"#{file_sout.path}\" 2> \"#{file_serr.path}\""
225
+ begin
226
+ # Windows will have a conniption because the tempfiles are already open.
227
+ # We close them here and then open them to read them.
228
+ file_sout.close
229
+ file_serr.close
230
+
231
+ system(cmd)
232
+
233
+ file_sout.open
234
+ file_serr.open
235
+ sout, serr = file_sout.readlines, file_serr.readlines
236
+ file_sout.close
237
+ file_serr.close
238
+
239
+ yield(sout, serr)
240
+
241
+ ensure
242
+ file_sout.delete
243
+ file_serr.delete
244
+ end
245
+ end
246
+
247
+ # NOTE: Not used yet
248
+ # TODO: Use capture instead of capture_output
249
+ # Stolen from http://github.com/wycats/thor
250
+ def capture(stream)
251
+ begin
252
+ stream = stream.to_s
253
+ eval "$#{stream} = StringIO.new"
254
+ yield
255
+ result = eval("$#{stream}").string
256
+ ensure
257
+ eval("$#{stream} = #{stream.upcase}")
258
+ end
259
+
260
+ result
261
+ end
262
+
263
+
264
+ #
265
+ # Generates a string of random alphanumeric characters
266
+ # These are used as IDs throughout the system
267
+ def self.strand( len )
268
+ chars = ("a".."z").to_a + ("0".."9").to_a
269
+ newpass = ""
270
+ 1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
271
+ return newpass
272
+ end
273
+
274
+ end
275
+
276
+ end
@@ -0,0 +1,257 @@
1
+
2
+
3
+ module Stella
4
+ # Stella::SystemInfo
5
+ #
6
+ # A container for the system platform information.
7
+ # Portions of this code is from Amazon's EC2 AMI tools, lib/platform.rb.
8
+ class SystemInfo < Stella::Storable
9
+ IMPLEMENTATIONS = [
10
+
11
+ # These are for JRuby, System.getproperty('os.name').
12
+ # For a list of all values, see: http://lopica.sourceforge.net/os.html
13
+ [/mac\s*os\s*x/i, :unix, :osx ],
14
+ [/sunos/i, :unix, :solaris ],
15
+ [/windows\s*ce/i, :win32, :windows ],
16
+ [/windows/i, :win32, :windows ],
17
+ [/osx/i, :unix, :osx ],
18
+
19
+ # TODO: implement other windows matches: # /djgpp|(cyg|ms|bcc)win|mingw/ (from mongrel)
20
+
21
+ # These are for RUBY_PLATFORM and JRuby
22
+ [/java/i, :java, :java ],
23
+ [/darwin/i, :unix, :osx ],
24
+ [/linux/i, :unix, :linux ],
25
+ [/freebsd/i, :unix, :freebsd ],
26
+ [/netbsd/i, :unix, :netbsd ],
27
+ [/solaris/i, :unix, :solaris ],
28
+ [/irix/i, :unix, :irix ],
29
+ [/cygwin/i, :unix, :cygwin ],
30
+ [/mswin/i, :win32, :windows ],
31
+ [/mingw/i, :win32, :mingw ],
32
+ [/bccwin/i, :win32, :bccwin ],
33
+ [/wince/i, :win32, :wince ],
34
+ [/vms/i, :vms, :vms ],
35
+ [/os2/i, :os2, :os2 ],
36
+ [nil, :unknown, :unknown ],
37
+
38
+ ]
39
+
40
+ ARCHITECTURES = [
41
+ [/(i\d86)/i, :i386 ],
42
+ [/x86_64/i, :x86_64 ],
43
+ [/x86/i, :i386 ], # JRuby
44
+ [/ia64/i, :ia64 ],
45
+ [/alpha/i, :alpha ],
46
+ [/sparc/i, :sparc ],
47
+ [/mips/i, :mips ],
48
+ [/powerpc/i, :powerpc ],
49
+ [/universal/i,:universal ],
50
+ [nil, :unknown ],
51
+ ]
52
+
53
+
54
+
55
+ field :os => String
56
+ field :implementation => String
57
+ field :architecture => String
58
+ field :hostname => String
59
+ field :ipaddress => String
60
+ field :uptime => Float
61
+
62
+
63
+ alias :impl :implementation
64
+ alias :arch :architecture
65
+
66
+
67
+ def initialize
68
+ @os, @implementation, @architecture = guess
69
+ @hostname, @ipaddress, @uptime = get_info
70
+ end
71
+
72
+ # guess
73
+ #
74
+ # This is called at require-time in stella.rb. It guesses
75
+ # the current operating system, implementation, architecture.
76
+ # Returns [os, impl, arch]
77
+ def guess
78
+ os = :unknown
79
+ impl = :unknown
80
+ arch = :unknown
81
+ IMPLEMENTATIONS.each do |r, o, i|
82
+ if r and RUBY_PLATFORM =~ r
83
+ os, impl = [o, i]
84
+ break
85
+ end
86
+ end
87
+ ARCHITECTURES.each do |r, a|
88
+ if r and RUBY_PLATFORM =~ r
89
+ arch = a
90
+ break
91
+ end
92
+ end
93
+
94
+ #
95
+ if os == :win32
96
+ #require 'Win32API'
97
+
98
+ # If we're running in java, we'll need to look elsewhere
99
+ # for the implementation and architecture.
100
+ # We'll replace IMPL and ARCH with what we find.
101
+ elsif os == :java
102
+ require 'java'
103
+ include_class java.lang.System
104
+
105
+ osname = System.getProperty("os.name")
106
+ IMPLEMENTATIONS.each do |r, o, i|
107
+ if r and osname =~ r
108
+ impl = i
109
+ break
110
+ end
111
+ end
112
+
113
+ osarch = System.getProperty("os.arch")
114
+ ARCHITECTURES.each do |r, a|
115
+ if r and osarch =~ r
116
+ arch = a
117
+ break
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ [os, impl, arch]
124
+ end
125
+
126
+ # get_info
127
+ #
128
+ # Returns [hostname, ipaddr, uptime] for the local machine
129
+ def get_info
130
+ hostname = :unknown
131
+ ipaddr = :unknown
132
+ uptime = :unknown
133
+
134
+ begin
135
+ hostname = local_hostname
136
+ ipaddr = local_ip_address
137
+ uptime = local_uptime
138
+ rescue => ex
139
+ end
140
+
141
+ [hostname, ipaddr, uptime]
142
+ end
143
+
144
+ # local_hostname
145
+ #
146
+ # Return the hostname for the local machine
147
+ def local_hostname
148
+ Socket.gethostname
149
+ end
150
+
151
+ # local_uptime
152
+ #
153
+ # Returns the local uptime in hours. Use Win32API in Windows,
154
+ # 'sysctl -b kern.boottime' os osx, and 'who -b' on unix.
155
+ # Based on Ruby Quiz solutions by: Matthias Reitinger
156
+ # On Windows, see also: net statistics server
157
+ def local_uptime
158
+
159
+ # Each method must return uptime in seconds
160
+ methods = {
161
+
162
+ :win32_windows => lambda {
163
+ # Win32API is required in self.guess
164
+ getTickCount = Win32API.new("kernel32", "GetTickCount", nil, 'L')
165
+ ((getTickCount.call()).to_f / 1000).to_f
166
+ },
167
+
168
+ # Ya, this is kinda wack. Ruby -> Java -> Kernel32. See:
169
+ # http://www.oreillynet.com/ruby/blog/2008/01/jruby_meets_the_windows_api_1.html
170
+ # http://msdn.microsoft.com/en-us/library/ms724408(VS.85).aspx
171
+ # Ruby 1.9.1: Win32API is now deprecated in favor of using the DL library.
172
+ :java_windows => lambda {
173
+ kernel32 = com.sun.jna.NativeLibrary.getInstance('kernel32')
174
+ buf = java.nio.ByteBuffer.allocate(256)
175
+ (kernel32.getFunction('GetTickCount').invokeInt([256, buf].to_java).to_f / 1000).to_f
176
+ },
177
+
178
+ :unix_osx => lambda {
179
+ # This is faster than who and could work on BSD also.
180
+ (Time.now.to_f - Time.at(`sysctl -b kern.boottime 2>/dev/null`.unpack('L').first).to_f).to_f
181
+
182
+ },
183
+ # This should work for most unix flavours.
184
+ :unix => lambda {
185
+ # who is sloooooow. Use File.read('/proc/uptime')
186
+ (Time.now.to_f - Time.parse(`who -b 2>/dev/null`).to_f)
187
+ }
188
+ }
189
+
190
+ hours = 0
191
+
192
+ begin
193
+ key = platform
194
+ method = (methods.has_key? key) ? methods[key] : methods[:unix]
195
+ hours = (method.call) / 3600 # seconds to hours
196
+ rescue => ex
197
+ end
198
+ hours
199
+ end
200
+
201
+
202
+ # local_ip_address
203
+ #
204
+ # Return the local IP address which receives external traffic
205
+ # from: http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
206
+ # NOTE: This <em>does not</em> open a connection to the IP address.
207
+ def local_ip_address
208
+ # turn off reverse DNS resolution temporarily
209
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
210
+ UDPSocket.open {|s| s.connect('75.101.137.7', 1); s.addr.last } # Solutious IP
211
+ ensure
212
+ Socket.do_not_reverse_lookup = orig
213
+ end
214
+
215
+ # local_ip_address_alt
216
+ #
217
+ # Returns the local IP address based on the hostname.
218
+ # According to coderrr (see comments on blog link above), this implementation
219
+ # doesn't guarantee that it will return the address for the interface external
220
+ # traffic goes through. It's also possible the hostname isn't resolvable to the
221
+ # local IP.
222
+ def local_ip_address_alt
223
+ ipaddr = :unknown
224
+ begin
225
+ saddr = Socket.getaddrinfo( Socket.gethostname, nil, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME)
226
+ ipaddr = saddr.select{|type| type[0] == 'AF_INET' }[0][3]
227
+ rescue => ex
228
+ end
229
+ ipaddr
230
+ end
231
+
232
+ # platform
233
+ #
234
+ # returns a symbol in the form: os_implementation. This is used throughout Stella
235
+ # for platform specific support.
236
+ def platform
237
+ "#{@os}_#{@implementation}".to_sym
238
+ end
239
+
240
+ # ruby
241
+ #
242
+ # Returns Ruby version as an array
243
+ def ruby
244
+ RUBY_VERSION.split('.').map { |v| v.to_i }
245
+ end
246
+
247
+ # to_s
248
+ #
249
+ # Print friendly system information.
250
+ def to_s
251
+ sprintf("Hostname: %s#{$/}IP Address: %s#{$/}System: %s#{$/}Uptime: %.2f (hours)#{$/}Ruby: #{ruby.join('.')}",
252
+ @hostname, @ipaddress, "#{@os}-#{@implementation}-#{@architecture}", @uptime)
253
+ end
254
+
255
+
256
+ end
257
+ end