stella 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/{README.txt → README.textile} +63 -40
  2. data/Rakefile +7 -5
  3. data/bin/stella +1 -1
  4. data/bin/stella.bat +12 -0
  5. data/lib/pcaplet.rb +180 -0
  6. data/lib/stella/adapter/ab.rb +57 -33
  7. data/lib/stella/adapter/base.rb +11 -1
  8. data/lib/stella/adapter/httperf.rb +13 -10
  9. data/lib/stella/adapter/pcap_watcher.rb +221 -0
  10. data/lib/stella/adapter/proxy_watcher.rb +76 -0
  11. data/lib/stella/adapter/siege.rb +28 -11
  12. data/lib/stella/cli/agents.rb +2 -2
  13. data/lib/stella/cli/base.rb +37 -1
  14. data/lib/stella/cli/localtest.rb +1 -2
  15. data/lib/stella/cli/sysinfo.rb +17 -0
  16. data/lib/stella/cli/watch.rb +278 -0
  17. data/lib/stella/cli.rb +23 -11
  18. data/lib/stella/command/base.rb +1 -10
  19. data/lib/stella/command/localtest.rb +43 -23
  20. data/lib/stella/data/domain.rb +75 -0
  21. data/lib/stella/data/http.rb +124 -0
  22. data/lib/stella/logger.rb +16 -5
  23. data/lib/stella/storable.rb +4 -2
  24. data/lib/stella/support.rb +71 -0
  25. data/lib/stella/sysinfo.rb +247 -0
  26. data/lib/stella/test/base.rb +5 -1
  27. data/lib/stella/test/definition.rb +1 -1
  28. data/lib/stella/test/run/summary.rb +14 -4
  29. data/lib/stella/text/resource.rb +0 -1
  30. data/lib/stella.rb +28 -10
  31. data/lib/utils/domainutil.rb +47 -0
  32. data/lib/utils/fileutil.rb +22 -3
  33. data/lib/utils/httputil.rb +184 -128
  34. data/lib/utils/mathutil.rb +20 -7
  35. data/lib/win32/Console/ANSI.rb +305 -0
  36. data/lib/win32/Console.rb +970 -0
  37. data/spec/show-agents_spec.rb +0 -0
  38. data/support/kvm.h +91 -0
  39. data/support/ruby-pcap-takuma-notes.txt +19 -0
  40. data/support/ruby-pcap-takuma-patch.txt +30 -0
  41. data/support/text/en.yaml +26 -3
  42. data/vendor/frylock/README.textile +72 -0
  43. data/vendor/frylock/bin/example +170 -0
  44. data/vendor/frylock/frylock.gemspec +18 -0
  45. data/vendor/frylock/lib/frylock/exceptions.rb +24 -0
  46. data/vendor/frylock/lib/frylock.rb +232 -0
  47. data/vendor/frylock/test/command_test.rb +33 -0
  48. data/vendor/hitimes-0.4.0/HISTORY +28 -0
  49. data/vendor/hitimes-0.4.0/LICENSE.txt +19 -0
  50. data/vendor/hitimes-0.4.0/README +80 -0
  51. data/vendor/hitimes-0.4.0/Rakefile +63 -0
  52. data/vendor/hitimes-0.4.0/examples/benchmarks.rb +86 -0
  53. data/vendor/hitimes-0.4.0/examples/stats.rb +29 -0
  54. data/vendor/hitimes-0.4.0/ext/extconf.rb +15 -0
  55. data/vendor/hitimes-0.4.0/ext/hitimes_ext.c +21 -0
  56. data/vendor/hitimes-0.4.0/ext/hitimes_instant_clock_gettime.c +20 -0
  57. data/vendor/hitimes-0.4.0/ext/hitimes_instant_osx.c +16 -0
  58. data/vendor/hitimes-0.4.0/ext/hitimes_instant_windows.c +27 -0
  59. data/vendor/hitimes-0.4.0/ext/hitimes_interval.c +340 -0
  60. data/vendor/hitimes-0.4.0/ext/hitimes_interval.h +73 -0
  61. data/vendor/hitimes-0.4.0/ext/hitimes_stats.c +242 -0
  62. data/vendor/hitimes-0.4.0/ext/hitimes_stats.h +30 -0
  63. data/vendor/hitimes-0.4.0/ext/rbconfig-mingw.rb +178 -0
  64. data/vendor/hitimes-0.4.0/ext/rbconfig.rb +178 -0
  65. data/vendor/hitimes-0.4.0/gemspec.rb +54 -0
  66. data/vendor/hitimes-0.4.0/lib/hitimes/mutexed_stats.rb +23 -0
  67. data/vendor/hitimes-0.4.0/lib/hitimes/paths.rb +54 -0
  68. data/vendor/hitimes-0.4.0/lib/hitimes/stats.rb +29 -0
  69. data/vendor/hitimes-0.4.0/lib/hitimes/timer.rb +223 -0
  70. data/vendor/hitimes-0.4.0/lib/hitimes/version.rb +42 -0
  71. data/vendor/hitimes-0.4.0/lib/hitimes.rb +24 -0
  72. data/vendor/hitimes-0.4.0/spec/interval_spec.rb +115 -0
  73. data/vendor/hitimes-0.4.0/spec/mutex_stats_spec.rb +34 -0
  74. data/vendor/hitimes-0.4.0/spec/paths_spec.rb +14 -0
  75. data/vendor/hitimes-0.4.0/spec/spec_helper.rb +6 -0
  76. data/vendor/hitimes-0.4.0/spec/stats_spec.rb +72 -0
  77. data/vendor/hitimes-0.4.0/spec/timer_spec.rb +105 -0
  78. data/vendor/hitimes-0.4.0/spec/version_spec.rb +27 -0
  79. data/vendor/hitimes-0.4.0/tasks/announce.rake +39 -0
  80. data/vendor/hitimes-0.4.0/tasks/config.rb +107 -0
  81. data/vendor/hitimes-0.4.0/tasks/distribution.rake +53 -0
  82. data/vendor/hitimes-0.4.0/tasks/documentation.rake +33 -0
  83. data/vendor/hitimes-0.4.0/tasks/extension.rake +64 -0
  84. data/vendor/hitimes-0.4.0/tasks/rspec.rake +31 -0
  85. data/vendor/hitimes-0.4.0/tasks/rubyforge.rake +52 -0
  86. data/vendor/hitimes-0.4.0/tasks/utils.rb +80 -0
  87. data/vendor/useragent/lib/user_agent.rb +1 -1
  88. metadata +87 -8
@@ -0,0 +1,221 @@
1
+
2
+ require 'webrick'
3
+ require 'stringio'
4
+ require 'net/dns/packet'
5
+
6
+
7
+ module Stella
8
+ module Adapter
9
+ # Make sure Stella's lib directory is before the system defined ones.
10
+ # We are using a modified version of pcaplet.rb.
11
+ require 'pcap'
12
+ require 'observer'
13
+
14
+ # Stella::Adapter::PcapWatcher
15
+ #
16
+ # Record HTTP or DNS events with Pcap (TCP sniffer). This requires ruby-pcap and the C pcap
17
+ # library as well as root acceess (TCP packet sniffing requires root privileges). If you're
18
+ # running Ruby 1.9, JRuby, or Windows this will not be available on your system.
19
+ # To sniff traffic, you must be on either the machine sending the requests or the machine
20
+ # receiving the requests.
21
+ class PcapWatcher
22
+ include Observable
23
+
24
+ # Building Ruby::Pcap with Ruby 1.9.1
25
+ # RSTRING()->len ia now RSTRING_LEN(), ...
26
+ # see: http://gnufied.org/2007/12/21/mysql-c-bindings-for-ruby-19/#comment-3133
27
+ # see: http://www.rubyinside.com/ruby-1-9-1-preview-released-why-its-a-big-deal-1280.html#comment-37223
28
+ # TRAP_BEG and TRAP_END are also fucked. But the fix is not clear.
29
+ # Basically Ruby::PCap is not ready for 1.9
30
+ # See: http://d.hatena.ne.jp/takuma104/20080210/1202638583
31
+
32
+ # Network interface device ID. eri0, en0, lo0, etc... /sbin/ifconfig -a will tell you.
33
+ attr_accessor :device
34
+ # Buffer size, in bytes, to read from each packet (default: 1500)
35
+ attr_accessor :snaplen
36
+ # Port of the machine sending requests (default: 80)
37
+ attr_accessor :sport
38
+ # Port of the machine receiving requests (default: 80)
39
+ attr_accessor :dport
40
+ # dns or http
41
+ attr_accessor :service
42
+ # udp or tcp
43
+ attr_accessor :protocol
44
+ # Maximum number of packets to sniff
45
+ attr_accessor :maxpacks
46
+
47
+ attr_reader :pcaplet
48
+
49
+ def initialize(options={})
50
+ # The proper service name for dns is "domain"
51
+ @service = options[:service] || 'http'
52
+ @service = 'domain' if options[:service] == 'dns'
53
+
54
+ if @service == 'domain'
55
+ @protocol = 'udp'
56
+ else
57
+ @protocol = options[:protocol] || 'tcp'
58
+ end
59
+
60
+ @dport = options[:port] || Socket::getservbyname(@service)
61
+ @sport = options[:port] || @dport
62
+
63
+ @device = options[:device] || guess_device
64
+ @snaplen = options[:snaplen] || 1500 # 10KB
65
+ @maxpacks = options[:maxpacks] || 100_000
66
+
67
+ Stella::LOGGER.info("Watching interface #{@device} for #{@service} activity on #{@protocol} port #{@dport}")
68
+ end
69
+
70
+ def guess_device
71
+ # NOTE: This should be passed in as a value, not called from the global
72
+ case Stella::SYSINFO.implementation
73
+ when :osx
74
+ "en1" # Pcap.lookupdev returns en0
75
+ else
76
+ Pcap.lookupdev
77
+ end
78
+ end
79
+
80
+ def run
81
+
82
+ if (respond_to? "monitor_#{@service}")
83
+ self.send("monitor_#{@service}")
84
+ else
85
+ raise "Unknown service type (#{@service})"
86
+ end
87
+
88
+ end
89
+
90
+
91
+
92
+ # monitor_domain
93
+ #
94
+ # Use Ruby-Pcap to sniff packets off the network interface.
95
+ #
96
+ # DNS monitor based on: http://www.linuxjournal.com/article/9614
97
+ # Install http://rubyforge.org/projects/net-dns
98
+ #
99
+ # NOTE: Is there a better way to match up a request packet with a
100
+ # response packet?
101
+ # We keep connect a request with the response using the domain name.
102
+ # It's possible that two (or more) requests to be made for the same domain
103
+ # at the same time and the responses could be mixed up. This will affect
104
+ # the exact response time but probably not by much.
105
+ def monitor_domain
106
+
107
+ @pcaplet = Pcaplet.new(:device => @device, :count => @maxpacks)
108
+
109
+ req_filter = Pcap::Filter.new("#{@protocol} and dst port #{@dport}", @pcaplet.capture)
110
+ resp_filter = Pcap::Filter.new("#{@protocol} and src port #{@dport}", @pcaplet.capture)
111
+ @pcaplet.add_filter(req_filter | resp_filter)
112
+ @pcaplet.each_packet do |packet|
113
+ data = packet.udp_data
114
+ case packet
115
+ when req_filter
116
+ dobj = Stella::Data::DomainRequest.new(data)
117
+ dobj.time = packet.time
118
+ dobj.client_ip = packet.ip_src
119
+ dobj.server_ip = packet.ip_dst
120
+
121
+ changed
122
+ notify_observers(:domain_request, dobj)
123
+
124
+ when resp_filter
125
+ dobj = Stella::Data::DomainResponse.new(data)
126
+ dobj.time = packet.time
127
+ dobj.client_ip = packet.ip_dst
128
+ dobj.server_ip = packet.ip_src
129
+
130
+ changed
131
+ notify_observers(:domain_response, dobj)
132
+ end
133
+
134
+ end
135
+
136
+ rescue Interrupt
137
+ after
138
+ exit
139
+ end
140
+
141
+
142
+ def monitor_http
143
+
144
+ @pcaplet = Pcaplet.new(:device => @device, :count => @maxpacks)
145
+
146
+ begin
147
+ req_filter = Pcap::Filter.new("#{@protocol} and dst port #{@dport}", @pcaplet.capture)
148
+ resp_filter = Pcap::Filter.new("#{@protocol} and src port #{@sport}", @pcaplet.capture)
149
+ @pcaplet.add_filter(req_filter | resp_filter)
150
+ @pcaplet.each_packet do |packet|
151
+ data = packet.tcp_data
152
+ next if data.nil?
153
+
154
+
155
+ # NOTE: With HTTP 1.1 keep alive connections, multiple requests can be passed
156
+ # through single connection. This makes it difficult to match responses with
157
+ # requests.
158
+ # NOTE: We don't parse the body of POST and PUT requests because the data can
159
+ # be (and likely is), split across numerous packets. We also only grab 1500
160
+ # bytes from each packet.
161
+ # NOTE: The hostname is taken from the Host header. Requests made without
162
+ # this header (including HTTP 1.0) will contain the local hostname instead.
163
+ # TODO: resolve the hostname from the IP address.
164
+ # There are some helpful methods for doing some of this stuff:
165
+ # http://www.goto.info.waseda.ac.jp/~fukusima/ruby/pcap/doc/TCPPacket.html
166
+ case packet
167
+ when req_filter
168
+ next unless data and data =~ /^(GET|POST|HEAD|DELETE|PUT)\s+(.+?)\s+(HTTP.+?)$/
169
+ dobj = Stella::Data::HTTPRequest.new(data.gsub(/\r?\n/, $/)) # Use the system's line terminators
170
+ dobj.time = packet.time
171
+ dobj.client_ip = packet.ip_src
172
+ dobj.server_ip = packet.ip_dst
173
+
174
+ changed
175
+ notify_observers(:http_request, dobj)
176
+
177
+ when resp_filter
178
+ # NOTE: Some responses do not contain a body in the first packet.
179
+ # TODO: investigate further. Try: http://www.ruby-doc.org/core/classes/Enumerable.html
180
+ next unless data and data =~ /^(HTTP.+)$/
181
+ dobj = Stella::Data::HTTPResponse.new(data.gsub(/\r?\n/, $/))
182
+ dobj.time = packet.time
183
+ dobj.client_ip = packet.ip_dst
184
+ dobj.server_ip = packet.ip_src
185
+
186
+ changed
187
+ notify_observers(:http_response, dobj)
188
+
189
+ end
190
+
191
+ end
192
+ rescue Interrupt
193
+ after
194
+ exit
195
+ rescue => ex
196
+ Stella::LOGGER.error(ex)
197
+ end
198
+
199
+
200
+ end
201
+
202
+ def after
203
+ STDOUT.flush
204
+ stat = @pcaplet.capture.stats
205
+ if stat
206
+ Stella::LOGGER.info("#{$/}#{stat.recv} packets received by filter");
207
+ Stella::LOGGER.info("#{stat.drop} packets dropped by kernel", ''); # with an extra line
208
+ end
209
+ STDOUT.flush
210
+ @pcaplet.capture.close
211
+ delete_observers
212
+ rescue
213
+
214
+ # Ignore errors
215
+ end
216
+
217
+
218
+ end
219
+ end
220
+ end
221
+
@@ -0,0 +1,76 @@
1
+
2
+ require 'webrick/httpproxy'
3
+ require 'observer'
4
+
5
+
6
+ module Stella
7
+ module Adapter
8
+
9
+ # Stella::Adapter::ProxyWatcher
10
+ #
11
+ # Starts up an HTTP proxy using WEBrick to record HTTP events. This is used
12
+ # when PcapRecorder is not available.
13
+ class ProxyWatcher
14
+ include Observable
15
+
16
+ attr_accessor :port
17
+
18
+ def initialize(options={})
19
+ @port = options[:port]
20
+ end
21
+ require 'pp'
22
+ def run
23
+
24
+ @server = WEBrick::HTTPProxyServer.new(
25
+ :Port => @port || 3114,
26
+ :AccessLog => [],
27
+ :ProxyContentHandler => Proc.new do |req,res|
28
+
29
+ begin
30
+ res_obj = Stella::Data::HTTPResponse.new(res.to_s)
31
+ res_obj.time = Time.now
32
+
33
+ req_obj = Stella::Data::HTTPRequest.new(req.to_s)
34
+ req_obj.time = Time.now
35
+ req_obj.client_ip = '0.0.0.0'
36
+ req_obj.server_ip = '0.0.0.0'
37
+
38
+ req_obj.response = res_obj
39
+
40
+ changed
41
+ notify_observers(:http_request, req_obj)
42
+
43
+ rescue SystemExit => ex
44
+ after
45
+
46
+ rescue Exception => ex
47
+ # There are miscellaneous errors (mostly to do with
48
+ # incorrect content-length) that we don't care about.
49
+ Stella::LOGGER.error(ex.message)
50
+ end
51
+
52
+ end
53
+ )
54
+
55
+ # We need to trap this INT to kill WEBrick. Unlike with Pcap,
56
+ # rescuing Interrupt doesn't work.
57
+
58
+ trap('INT') do
59
+ after
60
+ end
61
+
62
+ @server.start
63
+ after
64
+
65
+ rescue => ex
66
+ after
67
+ end
68
+
69
+ def after
70
+ delete_observers
71
+ @server.shutdown
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -32,27 +32,37 @@ module Stella
32
32
  # -A, --user-agent="text" Sets User-Agent in request
33
33
  class Siege < Stella::Adapter::Base
34
34
 
35
-
36
- attr_accessor :version, :help, :config, :verbose, :get, :log, :mark, :delay, :header, :user_agent
37
- attr_accessor :reps, :concurrent, :rc, :file, :time, :benchmark, :internet
35
+ attr_writer :reps, :concurrent, :version
36
+ attr_reader :user_agent
37
+ attr_accessor :help, :config, :verbose, :get, :log, :mark, :delay, :header
38
+ attr_accessor :rc, :file, :time, :benchmark, :internet
38
39
 
39
40
  def initialize(options={}, arguments=[])
40
41
  super(options, arguments)
41
42
  @name = 'siege'
42
43
  @reps = 1
43
44
  @concurrent = 1
44
- @rc = File.join(ENV['HOME'], '.siegerc')
45
45
  @private_variables = ['private_variables', 'name', 'arguments', 'load_factor', 'working_directory', 'orig_logfile']
46
46
  @load_factor = 1
47
+
48
+ @rc = File.join(ENV['HOME'] || ENV['USERPROFILE'], '.siegerc')
49
+
50
+ # Siege won't run unless there's a siegerc file. If the default one doesn't exist
51
+ # we need to call siege.config to create it. This should only happen once.
52
+ # We use capture_output here so STDOUT and STDERR don't print to the screen.
53
+ Stella::Util.capture_output("#{@name}.config") do 'nothing' end unless File.exists? @rc
47
54
  end
48
55
 
49
56
 
57
+
58
+ def error
59
+ (File.exists? stderr_path) ? FileUtil.read_file(stderr_path) : "Unknown error"
60
+ end
61
+
50
62
  def version
51
63
  vsn = 0
52
- text = ""
53
- Open3.popen3("#{@name} --version") do |stdin, stdout, stderr|
54
- text = stderr.readlines.join
55
- text.scan(/SIEGE (\d+?\.\d+)/) { |v| vsn = v[0] }
64
+ Stella::Util.capture_output("#{@name} --version") do |stdout, stderr|
65
+ stderr.join.scan(/SIEGE (\d+?\.\d+)/) { |v| vsn = v[0] }
56
66
  end
57
67
  vsn
58
68
  end
@@ -137,8 +147,15 @@ module Stella
137
147
  opts.on('-i', '--internet') do |v| @internet = true; end
138
148
  opts.on('-A S', '--user-agent=S', String) do |v| @user_agent ||= []; @user_agent << v end
139
149
 
140
- raise "You cannot select both --internet and --benchmark" if options.internet && options.benchmark
141
-
150
+ unless options.benchmark
151
+ Stella::LOGGER.warn('--benchmark (or -b) is not selected. Siege will include "think-time" for all requests.')
152
+ end
153
+
154
+ opts.on('-n N',Integer) do |v|
155
+ Stella::LOGGER.error("-n is not a Siege parameter. You probably want -r.")
156
+ exit 1
157
+ end
158
+
142
159
  # parse! removes the options it finds.
143
160
  # It also fails when it finds unknown switches (i.e. -X)
144
161
  # Which should leave only the remaining arguments (URIs in this case)
@@ -257,7 +274,7 @@ module Stella
257
274
 
258
275
  # Siege writes the summary to STDERR
259
276
  def stats_file
260
- File.new(stderr_path)
277
+ File.new(stderr_path) if File.exists?(stderr_path)
261
278
  end
262
279
 
263
280
  def rc_file
@@ -42,8 +42,8 @@ module Stella
42
42
  end
43
43
  end
44
44
 
45
- puts (@list) ? agents.uniq.sort.join("\n") : Stella::TEXT.msg(:agents_count_message, agents.uniq.size)
46
-
45
+ msg = (@list) ? agents.uniq.sort.join("\n") : Stella::TEXT.msg(:agents_count_message, agents.uniq.size)
46
+ puts msg
47
47
  end
48
48
 
49
49
  def process_options(display=false)
@@ -13,7 +13,43 @@ module Stella
13
13
  def initialize(adapter)
14
14
  @adapter_name = adapter
15
15
  @options = OpenStruct.new
16
+
17
+ # There is a bug in Ruby 1.8.6 where a trapped SIGINT will hang.
18
+ # This workaround is from: http://redmine.ruby-lang.org/issues/show/362
19
+ # It works in Ruby 1.9 and JRuby as well.
20
+ # NEW WARNING: This puts the process into a new thread which somehow
21
+ # prevents Pcap from reporting on UDP/DNS packets (TCP/HTTP is unaffected).
22
+ # I left this here as an example of how not to it. Incidentally,
23
+ # "rescue Interrupt" seems to be working fine now.
24
+ # See also: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/220937
25
+ #@killer = Thread.new do
26
+ # puts "#{$/}Exiting...#{$/}"
27
+ # Thread.stop
28
+ # Thread.main.join(1)
29
+ # exit 0
30
+ #end
31
+ #
32
+ # Note that this is just a default. Any class that implements another
33
+ # Signal.trap will override this.
34
+ #Signal.trap('INT') do
35
+ # @killer.call
36
+ # exit 0
37
+ #end
16
38
  end
17
39
  end
18
40
  end
19
- end
41
+ end
42
+
43
+ __END__
44
+
45
+ TODO: Investigate frylock style definition
46
+ include Stella::CLI::Base
47
+
48
+ before do
49
+ # stuff that would go in initialize
50
+ end
51
+
52
+ command 'sysinfo' do
53
+ puts Stella::SYSINFO.to_yaml(:headers)
54
+ end
55
+
@@ -45,7 +45,6 @@ module Stella
45
45
  @driver.working_directory = @working_directory
46
46
 
47
47
  @driver.run
48
-
49
48
  end
50
49
 
51
50
 
@@ -54,7 +53,7 @@ module Stella
54
53
  #
55
54
  # Populates @testdef with values from @stella_options
56
55
  def process_stella_options
57
- @testdef.repetitions = @stella_options.testreps
56
+ @testdef.repetitions = @stella_options.repetitions
58
57
  @testdef.sleep = @stella_options.sleep
59
58
  @testdef.warmup = @stella_options.warmup
60
59
  @testdef.rampup = @stella_options.rampup
@@ -0,0 +1,17 @@
1
+
2
+ module Stella
3
+ class CLI
4
+ class SystemInfo < Stella::CLI::Base
5
+
6
+
7
+ def run
8
+ puts Stella::SYSINFO
9
+ #puts "HI"
10
+ end
11
+
12
+ end
13
+
14
+ @@commands['sysinfo'] = Stella::CLI::SystemInfo
15
+ end
16
+ end
17
+