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